-
Notifications
You must be signed in to change notification settings - Fork 52
Add L1 multi-builder recipe #418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
e16f7d9
7cb7e02
4b265f9
25c8a40
c443155
ce0d122
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # l1-multi-builder Recipe | ||
|
|
||
| Deploy a full L1 stack with multiple builders and relays. | ||
|
|
||
| ## Flags | ||
|
|
||
| - `block-time` (duration): Block time to use for the L1. Default to '12s'. | ||
| - `builders` (int): number of rbuilder instances to run. Default to '2'. | ||
| - `latest-fork` (bool): use the latest fork. Default to 'false'. | ||
| - `relays` (int): number of mev-boost-relay instances to run. Default to '2'. | ||
| - `use-reth-for-validation` (bool): use reth for validation. Default to 'false'. | ||
|
|
||
| ## Architecture Diagram | ||
|
|
||
| ```mermaid | ||
| graph LR | ||
| el["el<br/>rpc:30303<br/>http:8545<br/>ws:8546<br/>authrpc:8551<br/>metrics:9090"] | ||
| el_healthmon["el_healthmon"] | ||
| beacon["beacon<br/>p2p:9000<br/>p2p:9000<br/>quic-p2p:9100<br/>http:3500"] | ||
| beacon_healthmon["beacon_healthmon"] | ||
| validator["validator"] | ||
| mev_boost_relay_1["mev-boost-relay-1<br/>http:5555"] | ||
| mev_boost_relay_2["mev-boost-relay-2<br/>http:5555"] | ||
| mev_boost["mev-boost<br/>http:18550"] | ||
| rbuilder_1["rbuilder-1"] | ||
| rbuilder_2["rbuilder-2"] | ||
|
|
||
| el_healthmon -->|http| el | ||
| beacon -->|authrpc| el | ||
| beacon -->|http| mev_boost | ||
| beacon_healthmon -->|http| beacon | ||
| validator -->|http| beacon | ||
| mev_boost_relay_1 -->|http| beacon | ||
| mev_boost_relay_2 -->|http| beacon | ||
| mev_boost -->|http| mev_boost_relay_1 | ||
| mev_boost -->|http| mev_boost_relay_2 | ||
| mev_boost_relay_1 -.->|depends_on| beacon | ||
| mev_boost_relay_2 -.->|depends_on| beacon | ||
| rbuilder_1 -.->|depends_on| el | ||
| rbuilder_1 -.->|depends_on| beacon | ||
| rbuilder_2 -.->|depends_on| el | ||
| rbuilder_2 -.->|depends_on| beacon | ||
| ``` | ||
|
|
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -632,14 +632,23 @@ func (c *ClProxy) Apply(ctx *ExContext) *Component { | ||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| type MevBoostRelay struct { | |||||||||||||||||||||||
| ServiceName string | |||||||||||||||||||||||
| BeaconClient string | |||||||||||||||||||||||
| ValidationServer string | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (m *MevBoostRelay) serviceName() string { | |||||||||||||||||||||||
| if m.ServiceName != "" { | |||||||||||||||||||||||
| return m.ServiceName | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return "mev-boost-relay" | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (m *MevBoostRelay) Apply(ctx *ExContext) *Component { | |||||||||||||||||||||||
| component := NewComponent("mev-boost-relay") | |||||||||||||||||||||||
| serviceName := m.serviceName() | |||||||||||||||||||||||
| component := NewComponent(serviceName) | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| service := component.NewService("mev-boost-relay"). | |||||||||||||||||||||||
| service := component.NewService(serviceName). | |||||||||||||||||||||||
| WithImage("docker.io/flashbots/playground-utils"). | |||||||||||||||||||||||
| WithTag(latestPlaygroundUtilsTag). | |||||||||||||||||||||||
| WithEnv("ALLOW_SYNCING_BEACON_NODE", "1"). | |||||||||||||||||||||||
|
|
@@ -718,6 +727,27 @@ type MevBoost struct { | ||||||||||||||||||||||
| RelayEndpoints []string | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func localMevBoostRelayURL(endpoint string) (string, bool) { | |||||||||||||||||||||||
| envSkBytes, err := hexutil.Decode(mevboostrelay.DefaultSecretKey) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| return "", false | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| secretKey, err := bls.SecretKeyFromBytes(envSkBytes[:]) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| return "", false | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| blsPublicKey, err := bls.PublicKeyFromSecretKey(secretKey) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| return "", false | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| publicKey, err := utils.BlsPublicKeyToPublicKey(blsPublicKey) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| return "", false | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| return ConnectRaw(endpoint, "http", "http", publicKey.String()), true | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Multi-relay topology silently degenerates to one relay. This was raised in the previous review and is still unaddressed. Either:
|
|||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (m *MevBoost) Apply(ctx *ExContext) *Component { | |||||||||||||||||||||||
| component := NewComponent("mev-boost") | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
|
|
@@ -727,26 +757,9 @@ func (m *MevBoost) Apply(ctx *ExContext) *Component { | ||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| for _, endpoint := range m.RelayEndpoints { | |||||||||||||||||||||||
| if endpoint == "mev-boost-relay" { | |||||||||||||||||||||||
| // creating relay url with public key since mev-boost requires it | |||||||||||||||||||||||
| envSkBytes, err := hexutil.Decode(mevboostrelay.DefaultSecretKey) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| continue | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| secretKey, err := bls.SecretKeyFromBytes(envSkBytes[:]) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| continue | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| blsPublicKey, err := bls.PublicKeyFromSecretKey(secretKey) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| continue | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| publicKey, err := utils.BlsPublicKeyToPublicKey(blsPublicKey) | |||||||||||||||||||||||
| if err != nil { | |||||||||||||||||||||||
| continue | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| relayURL := ConnectRaw("mev-boost-relay", "http", "http", publicKey.String()) | |||||||||||||||||||||||
| if strings.Contains(endpoint, "://") { | |||||||||||||||||||||||
| args = append(args, "--relay", endpoint) | |||||||||||||||||||||||
| } else if relayURL, ok := localMevBoostRelayURL(endpoint); ok { | |||||||||||||||||||||||
| args = append(args, "--relay", relayURL) | |||||||||||||||||||||||
| } else { | |||||||||||||||||||||||
| args = append(args, "--relay", Connect(endpoint, "http")) | |||||||||||||||||||||||
|
|
@@ -762,28 +775,173 @@ func (m *MevBoost) Apply(ctx *ExContext) *Component { | ||||||||||||||||||||||
| return component | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| const defaultRbuilderRelaySecretKey = "0x25295f0d1d592a90b333e26e85149708208e9f8e8bc18f6c77bd62f8ad7a6866" | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| //go:embed utils/rbuilder-config.toml.tmpl | |||||||||||||||||||||||
| var rbuilderConfigToml string | |||||||||||||||||||||||
| var defaultRbuilderConfigToml string | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| type Rbuilder struct { | |||||||||||||||||||||||
| ServiceName string | |||||||||||||||||||||||
| BeaconNode string | |||||||||||||||||||||||
| ExecutionNode string | |||||||||||||||||||||||
| RelayEndpoints []string | |||||||||||||||||||||||
| RelaySecretKey string | |||||||||||||||||||||||
| ConfigArtifact string | |||||||||||||||||||||||
| ExtraData string | |||||||||||||||||||||||
| JSONRPCPort int | |||||||||||||||||||||||
| RedactedPort int | |||||||||||||||||||||||
| FullMetricsPort int | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) serviceName() string { | |||||||||||||||||||||||
| if r.ServiceName != "" { | |||||||||||||||||||||||
| return r.ServiceName | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return "rbuilder" | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) beaconNode() string { | |||||||||||||||||||||||
| if r.BeaconNode != "" { | |||||||||||||||||||||||
| return r.BeaconNode | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return "beacon" | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) executionNode() string { | |||||||||||||||||||||||
| if r.ExecutionNode != "" { | |||||||||||||||||||||||
| return r.ExecutionNode | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return "el" | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) configArtifact() string { | |||||||||||||||||||||||
| if r.ConfigArtifact != "" { | |||||||||||||||||||||||
| return r.ConfigArtifact | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| if r.ServiceName != "" { | |||||||||||||||||||||||
| return r.ServiceName + "-config.toml" | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return "rbuilder-config.toml" | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| type Rbuilder struct{} | |||||||||||||||||||||||
| func (r *Rbuilder) relaySecretKey() string { | |||||||||||||||||||||||
| if r.RelaySecretKey != "" { | |||||||||||||||||||||||
| return r.RelaySecretKey | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return defaultRbuilderRelaySecretKey | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) relayEndpoints() []string { | |||||||||||||||||||||||
| if len(r.RelayEndpoints) > 0 { | |||||||||||||||||||||||
| return r.RelayEndpoints | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return []string{"mev-boost-relay"} | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) extraData() string { | |||||||||||||||||||||||
| if r.ExtraData != "" { | |||||||||||||||||||||||
| return r.ExtraData | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return "Playground Builder" | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) jsonRPCPort() int { | |||||||||||||||||||||||
| if r.JSONRPCPort != 0 { | |||||||||||||||||||||||
| return r.JSONRPCPort | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return 8645 | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) redactedPort() int { | |||||||||||||||||||||||
| if r.RedactedPort != 0 { | |||||||||||||||||||||||
| return r.RedactedPort | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return 6061 | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) fullMetricsPort() int { | |||||||||||||||||||||||
| if r.FullMetricsPort != 0 { | |||||||||||||||||||||||
| return r.FullMetricsPort | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| return 6060 | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) configTOML() string { | |||||||||||||||||||||||
| relayEndpoints := r.relayEndpoints() | |||||||||||||||||||||||
| relayNames := make([]string, 0, len(relayEndpoints)) | |||||||||||||||||||||||
| for _, relay := range relayEndpoints { | |||||||||||||||||||||||
| relayNames = append(relayNames, strconv.Quote(relay)) | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| var b strings.Builder | |||||||||||||||||||||||
| fmt.Fprintf(&b, "log_json = false\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "log_level = \"info,rbuilder=debug\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "redacted_telemetry_server_port = %d\n", r.redactedPort()) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "redacted_telemetry_server_ip = \"0.0.0.0\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "full_telemetry_server_port = %d\n", r.fullMetricsPort()) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "full_telemetry_server_ip = \"0.0.0.0\"\n\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "chain = \"/data/genesis.json\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "reth_datadir = \"/data_reth\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "el_node_ipc_path = \"/data_reth/reth.ipc\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "coinbase_secret_key = \"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "relay_secret_key = %s\n", strconv.Quote(r.relaySecretKey())) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "cl_node_url = [%s]\n", strconv.Quote(fmt.Sprintf("http://%s:3500", r.beaconNode()))) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "jsonrpc_server_port = %d\n", r.jsonRPCPort()) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "jsonrpc_server_ip = \"0.0.0.0\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "extra_data = %s\n\n", strconv.Quote(r.extraData())) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "ignore_cancellable_orders = true\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "root_hash_use_sparse_trie = true\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "root_hash_compare_sparse_trie = false\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "slot_delta_to_start_bidding_ms = -20000\n\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "live_builders = [\"mp-ordering\"]\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "enabled_relays = [%s]\n\n", strings.Join(relayNames, ", ")) | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| for i, relay := range relayEndpoints { | |||||||||||||||||||||||
| fmt.Fprintf(&b, "[[relays]]\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "name = %s\n", strconv.Quote(relay)) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "url = %s\n", strconv.Quote(fmt.Sprintf("http://%s:5555", relay))) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "priority = %d\n", i) | |||||||||||||||||||||||
| fmt.Fprintf(&b, "use_ssz_for_submit = false\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "use_gzip_for_submit = false\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "mode = \"full\"\n\n") | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| fmt.Fprintf(&b, "[[builders]]\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "name = \"mp-ordering\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "algo = \"ordering-builder\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "discard_txs = true\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "sorting = \"max-profit\"\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "failed_order_retries = 1\n") | |||||||||||||||||||||||
| fmt.Fprintf(&b, "drop_failed_orders = true\n") | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| return b.String() | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Building TOML by hand with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hand-rolled TOML with quoting inconsistencies and a hidden coupling.
|
|||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| func (r *Rbuilder) Apply(ctx *ExContext) *Component { | |||||||||||||||||||||||
| component := NewComponent("rbuilder") | |||||||||||||||||||||||
| serviceName := r.serviceName() | |||||||||||||||||||||||
| configArtifact := r.configArtifact() | |||||||||||||||||||||||
| component := NewComponent(serviceName) | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| // TODO: Handle error | |||||||||||||||||||||||
| ctx.Output.WriteFile("rbuilder-config.toml", rbuilderConfigToml) | |||||||||||||||||||||||
| config := defaultRbuilderConfigToml | |||||||||||||||||||||||
| if r.ServiceName != "" || len(r.RelayEndpoints) > 0 || r.BeaconNode != "" || r.ExecutionNode != "" || | |||||||||||||||||||||||
| r.RelaySecretKey != "" || r.ExtraData != "" || r.JSONRPCPort != 0 || r.RedactedPort != 0 || r.FullMetricsPort != 0 { | |||||||||||||||||||||||
| config = r.configTOML() | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
| ctx.Output.WriteFile(configArtifact, config) | |||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This "if any field was set, regenerate the TOML programmatically; otherwise use the embedded template" gate is fragile: the template and
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two non-equivalent code paths for the same component. The "any field set → regenerate, otherwise use template" gate switches between TOMLs with materially different content:
So Pick one source of truth: either always call |
|||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| component.NewService("rbuilder"). | |||||||||||||||||||||||
| service := component.NewService(serviceName). | |||||||||||||||||||||||
| WithImage("ghcr.io/flashbots/rbuilder"). | |||||||||||||||||||||||
| WithTag("sha-7efdc0b"). | |||||||||||||||||||||||
| WithArtifact("/data/rbuilder-config.toml", "rbuilder-config.toml"). | |||||||||||||||||||||||
| WithArtifact("/data/rbuilder-config.toml", configArtifact). | |||||||||||||||||||||||
| WithArtifact("/data/genesis.json", "genesis.json"). | |||||||||||||||||||||||
| WithVolume("shared:el-data", "/data_reth", true). | |||||||||||||||||||||||
| DependsOnHealthy("el"). | |||||||||||||||||||||||
| DependsOnHealthy("beacon"). | |||||||||||||||||||||||
| DependsOnHealthy(r.executionNode()). | |||||||||||||||||||||||
| DependsOnHealthy(r.beaconNode()). | |||||||||||||||||||||||
| WithArgs( | |||||||||||||||||||||||
| "run", "/data/rbuilder-config.toml", | |||||||||||||||||||||||
| ) | |||||||||||||||||||||||
| service.Pid = "service:" + r.executionNode() | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
| return component | |||||||||||||||||||||||
| } | |||||||||||||||||||||||
|
|
|||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All
MevBoostRelayinstances in this repo run withmevboostrelay.DefaultSecretKey(seemev-boost-relay/cmd/main.go— the CLI exposes no--api-secret-keyflag), solocalMevBoostRelayURLreturns the same BLS pubkey for every endpoint passed to it. In the newl1-multi-builderrecipe this means mev-boost is configured with N URLs that all share one pubkey. mev-boost dedupes/aggregates by pubkey internally, so users will not actually see N independent relays — the multi-relay topology silently degenerates to one. Either expose--api-secret-keyon the relay CLI and assign per-relay keys here (similar to how rbuilder gets a per-builderRelaySecretKey), or document this limitation prominently.