Skip to content

Commit 26b3759

Browse files
MoeMahhoukferranbt
andauthored
feat: Add builder-hub component and buildernet-recipe (#76)
* feat: Add builder-hub component and buildernet-recipe * adapt to the newest changes from builder-hub * More fixes * Everything working * Add orderflow proxy sender component * Revert "Add orderflow proxy sender component" This reverts commit e97ccc6. --------- Co-authored-by: Ferran Borreguero <ferran.borreguero@gmail.com>
1 parent 61c22ed commit 26b3759

File tree

7 files changed

+222
-27
lines changed

7 files changed

+222
-27
lines changed

internal/artifacts.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,12 @@ func getPrivKey(privStr string) (*ecdsa.PrivateKey, error) {
341341
return priv, nil
342342
}
343343

344+
func ConnectRaw(service, port, protocol string) string {
345+
return fmt.Sprintf(`{{Service "%s" "%s" "%s"}}`, service, port, protocol)
346+
}
347+
344348
func Connect(service, port string) string {
345-
return fmt.Sprintf(`{{Service "%s" "%s"}}`, service, port)
349+
return ConnectRaw(service, port, "http")
346350
}
347351

348352
type output struct {

internal/catalog.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ func init() {
1717
register(&MevBoostRelay{})
1818
register(&RollupBoost{})
1919
register(&OpReth{})
20+
register(&BuilderHub{})
21+
register(&BuilderHubPostgres{})
22+
register(&BuilderHubMockProxy{})
2023
}
2124

2225
func FindComponent(name string) Service {

internal/components.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,70 @@ func (m *MevBoostRelay) Watchdog(out io.Writer, service *service, ctx context.Co
429429
return watchGroup.wait()
430430
}
431431

432+
type BuilderHubPostgres struct {
433+
}
434+
435+
func (b *BuilderHubPostgres) Run(service *service, ctx *ExContext) {
436+
service.
437+
WithImage("docker.io/flashbots/builder-hub-db").
438+
WithTag("latest").
439+
WithPort("postgres", 5432).
440+
WithEnv("POSTGRES_USER", "postgres").
441+
WithEnv("POSTGRES_PASSWORD", "postgres").
442+
WithEnv("POSTGRES_DB", "postgres").
443+
WithReady(ReadyCheck{
444+
Test: []string{"CMD-SHELL", "pg_isready -U postgres -d postgres"},
445+
Interval: 1 * time.Second,
446+
Timeout: 30 * time.Second,
447+
Retries: 3,
448+
StartPeriod: 1 * time.Second,
449+
})
450+
}
451+
452+
func (b *BuilderHubPostgres) Name() string {
453+
return "builder-hub-postgres"
454+
}
455+
456+
type BuilderHub struct {
457+
postgres string
458+
}
459+
460+
func (b *BuilderHub) Run(service *service, ctx *ExContext) {
461+
service.
462+
WithImage("docker.io/flashbots/builder-hub").
463+
WithTag("latest").
464+
WithEntrypoint("/app/builder-hub").
465+
WithEnv("POSTGRES_DSN", "postgres://postgres:postgres@"+ConnectRaw(b.postgres, "postgres", "")+"/postgres?sslmode=disable").
466+
WithEnv("LISTEN_ADDR", "0.0.0.0:"+`{{Port "http" 8080}}`).
467+
WithEnv("ADMIN_ADDR", "0.0.0.0:"+`{{Port "admin" 8081}}`).
468+
WithEnv("INTERNAL_ADDR", "0.0.0.0:"+`{{Port "internal" 8082}}`).
469+
WithEnv("METRICS_ADDR", "0.0.0.0:"+`{{Port "metrics" 8090}}`).
470+
DependsOnHealthy(b.postgres)
471+
}
472+
473+
func (b *BuilderHub) Name() string {
474+
return "builder-hub"
475+
}
476+
477+
type BuilderHubMockProxy struct {
478+
TargetService string
479+
}
480+
481+
func (b *BuilderHubMockProxy) Run(service *service, ctx *ExContext) {
482+
service.
483+
WithImage("docker.io/flashbots/builder-hub-mock-proxy").
484+
WithTag("latest").
485+
WithPort("http", 8888)
486+
487+
if b.TargetService != "" {
488+
service.DependsOnHealthy(b.TargetService)
489+
}
490+
}
491+
492+
func (b *BuilderHubMockProxy) Name() string {
493+
return "builder-hub-mock-proxy"
494+
}
495+
432496
type OpReth struct {
433497
}
434498

internal/local_runner.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ func (d *LocalRunner) getService(name string) *service {
333333

334334
// applyTemplate resolves the templates from the manifest (Dir, Port, Connect) into
335335
// the actual values for this specific docker execution.
336-
func (d *LocalRunner) applyTemplate(s *service) ([]string, error) {
336+
func (d *LocalRunner) applyTemplate(s *service) ([]string, map[string]string, error) {
337337
var input map[string]interface{}
338338

339339
// For {{.Dir}}:
@@ -350,7 +350,12 @@ func (d *LocalRunner) applyTemplate(s *service) ([]string, error) {
350350
}
351351

352352
funcs := template.FuncMap{
353-
"Service": func(name string, portLabel string) string {
353+
"Service": func(name string, portLabel, protocol string) string {
354+
protocolPrefix := ""
355+
if protocol == "http" {
356+
protocolPrefix = "http://"
357+
}
358+
354359
// For {{Service "name" "portLabel"}}:
355360
// - Service runs on host:
356361
// A: target is inside docker: access with localhost:hostPort
@@ -365,14 +370,14 @@ func (d *LocalRunner) applyTemplate(s *service) ([]string, error) {
365370

366371
if d.isHostService(s.Name) {
367372
// A and B
368-
return fmt.Sprintf("http://localhost:%d", port.HostPort)
373+
return fmt.Sprintf("%slocalhost:%d", protocolPrefix, port.HostPort)
369374
} else {
370375
if d.isHostService(svc.Name) {
371376
// D
372-
return fmt.Sprintf("http://host.docker.internal:%d", port.HostPort)
377+
return fmt.Sprintf("%shost.docker.internal:%d", protocolPrefix, port.HostPort)
373378
}
374379
// C
375-
return fmt.Sprintf("http://%s:%d", svc.Name, port.Port)
380+
return fmt.Sprintf("%s%s:%d", protocolPrefix, svc.Name, port.Port)
376381
}
377382
},
378383
"Port": func(name string, defaultPort int) int {
@@ -386,28 +391,48 @@ func (d *LocalRunner) applyTemplate(s *service) ([]string, error) {
386391
},
387392
}
388393

389-
var argsResult []string
390-
for _, arg := range s.args {
394+
runTemplate := func(arg string) (string, error) {
391395
tpl, err := template.New("").Funcs(funcs).Parse(arg)
392396
if err != nil {
393-
return nil, err
397+
return "", err
394398
}
395399

396400
var out strings.Builder
397401
if err := tpl.Execute(&out, input); err != nil {
398-
return nil, err
402+
return "", err
403+
}
404+
405+
return out.String(), nil
406+
}
407+
408+
// apply the templates to the arguments
409+
var argsResult []string
410+
for _, arg := range s.args {
411+
newArg, err := runTemplate(arg)
412+
if err != nil {
413+
return nil, nil, err
414+
}
415+
argsResult = append(argsResult, newArg)
416+
}
417+
418+
// apply the templates to the environment variables
419+
envs := map[string]string{}
420+
for k, v := range s.env {
421+
newV, err := runTemplate(v)
422+
if err != nil {
423+
return nil, nil, err
399424
}
400-
argsResult = append(argsResult, out.String())
425+
envs[k] = newV
401426
}
402427

403-
return argsResult, nil
428+
return argsResult, envs, nil
404429
}
405430

406431
func (d *LocalRunner) toDockerComposeService(s *service) (map[string]interface{}, error) {
407432
// apply the template again on the arguments to figure out the connections
408433
// at this point all of them are valid, we just have to resolve them again. We assume for now
409434
// everyone is going to be on docker at the same network.
410-
args, err := d.applyTemplate(s)
435+
args, envs, err := d.applyTemplate(s)
411436
if err != nil {
412437
return nil, fmt.Errorf("failed to apply template, err: %w", err)
413438
}
@@ -433,8 +458,8 @@ func (d *LocalRunner) toDockerComposeService(s *service) (map[string]interface{}
433458
"labels": map[string]string{"playground": "true"},
434459
}
435460

436-
if len(s.env) > 0 {
437-
service["environment"] = s.env
461+
if len(envs) > 0 {
462+
service["environment"] = envs
438463
}
439464

440465
if s.readyCheck != nil {
@@ -544,7 +569,8 @@ func (d *LocalRunner) generateDockerCompose() ([]byte, error) {
544569

545570
// runOnHost runs the service on the host machine
546571
func (d *LocalRunner) runOnHost(ss *service) error {
547-
args, err := d.applyTemplate(ss)
572+
// TODO: Use env vars in host processes
573+
args, _, err := d.applyTemplate(ss)
548574
if err != nil {
549575
return fmt.Errorf("failed to apply template, err: %w", err)
550576
}

internal/manifest.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ func (s *service) WithEnv(key, value string) *service {
332332
if s.env == nil {
333333
s.env = make(map[string]string)
334334
}
335+
s.applyTemplate(value)
335336
s.env[key] = value
336337
return s
337338
}
@@ -378,17 +379,21 @@ func (s *service) WithPort(name string, portNumber int) *service {
378379
return s
379380
}
380381

382+
func (s *service) applyTemplate(arg string) {
383+
var port []Port
384+
var nodeRef []NodeRef
385+
_, port, nodeRef = applyTemplate(arg)
386+
for _, p := range port {
387+
s.WithPort(p.Name, p.Port)
388+
}
389+
for _, n := range nodeRef {
390+
s.nodeRefs = append(s.nodeRefs, &n)
391+
}
392+
}
393+
381394
func (s *service) WithArgs(args ...string) *service {
382-
for i, arg := range args {
383-
var port []Port
384-
var nodeRef []NodeRef
385-
args[i], port, nodeRef = applyTemplate(arg)
386-
for _, p := range port {
387-
s.WithPort(p.Name, p.Port)
388-
}
389-
for _, n := range nodeRef {
390-
s.nodeRefs = append(s.nodeRefs, &n)
391-
}
395+
for _, arg := range args {
396+
s.applyTemplate(arg)
392397
}
393398
s.args = append(s.args, args...)
394399
return s
@@ -419,6 +424,8 @@ func (s *service) DependsOnRunning(name string) *service {
419424
}
420425

421426
func applyTemplate(templateStr string) (string, []Port, []NodeRef) {
427+
// TODO: Can we remove the return argument string?
428+
422429
// use template substitution to load constants
423430
// pass-through the Dir template because it has to be resolved at the runtime
424431
input := map[string]interface{}{
@@ -430,7 +437,7 @@ func applyTemplate(templateStr string) (string, []Port, []NodeRef) {
430437
// ther can be multiple port and nodere because in the case of op-geth we pass a whole string as nested command args
431438

432439
funcs := template.FuncMap{
433-
"Service": func(name string, portLabel string) string {
440+
"Service": func(name string, portLabel, protocol string) string {
434441
if name == "" {
435442
panic("BUG: service name cannot be empty")
436443
}

internal/recipe_buildernet.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
6+
flag "github.com/spf13/pflag"
7+
)
8+
9+
var _ Recipe = &BuilderNetRecipe{}
10+
11+
// BuilderNetRecipe is a recipe that extends the L1 recipe to include builder-hub
12+
type BuilderNetRecipe struct {
13+
// Embed the L1Recipe to reuse its functionality
14+
l1Recipe L1Recipe
15+
16+
// Add mock proxy for testing
17+
includeMockProxy bool
18+
}
19+
20+
func (b *BuilderNetRecipe) Name() string {
21+
return "buildernet"
22+
}
23+
24+
func (b *BuilderNetRecipe) Description() string {
25+
return "Deploy a full L1 stack with mev-boost and builder-hub"
26+
}
27+
28+
func (b *BuilderNetRecipe) Flags() *flag.FlagSet {
29+
// Reuse the L1Recipe flags
30+
flags := b.l1Recipe.Flags()
31+
32+
// Add a flag to enable/disable the mock proxy
33+
flags.BoolVar(&b.includeMockProxy, "mock-proxy", false, "include a mock proxy for builder-hub with attestation headers")
34+
35+
return flags
36+
}
37+
38+
func (b *BuilderNetRecipe) Artifacts() *ArtifactsBuilder {
39+
// Reuse the L1Recipe artifacts builder
40+
return b.l1Recipe.Artifacts()
41+
}
42+
43+
func (b *BuilderNetRecipe) Apply(ctx *ExContext, artifacts *Artifacts) *Manifest {
44+
// Start with the L1Recipe manifest
45+
svcManager := b.l1Recipe.Apply(ctx, artifacts)
46+
47+
// Add builder-hub-postgres service (now includes migrations)
48+
svcManager.AddService("builder-hub-postgres", &BuilderHubPostgres{})
49+
50+
// Add the builder-hub service
51+
svcManager.AddService("builder-hub", &BuilderHub{
52+
postgres: "builder-hub-postgres",
53+
})
54+
55+
// Optionally add mock proxy for testing
56+
if b.includeMockProxy {
57+
svcManager.AddService("builder-hub-proxy", &BuilderHubMockProxy{
58+
TargetService: "builder-hub",
59+
})
60+
}
61+
62+
return svcManager
63+
}
64+
65+
func (b *BuilderNetRecipe) Output(manifest *Manifest) map[string]interface{} {
66+
// Start with the L1Recipe output
67+
output := b.l1Recipe.Output(manifest)
68+
69+
// Add builder-hub service info
70+
builderHubService, ok := manifest.GetService("builder-hub")
71+
if ok {
72+
http := builderHubService.MustGetPort("http")
73+
admin := builderHubService.MustGetPort("admin")
74+
internal := builderHubService.MustGetPort("internal")
75+
76+
output["builder-hub-http"] = fmt.Sprintf("http://localhost:%d", http.HostPort)
77+
output["builder-hub-admin"] = fmt.Sprintf("http://localhost:%d", admin.HostPort)
78+
output["builder-hub-internal"] = fmt.Sprintf("http://localhost:%d", internal.HostPort)
79+
}
80+
81+
if b.includeMockProxy {
82+
proxyService, ok := manifest.GetService("builder-hub-proxy")
83+
if ok {
84+
http := proxyService.MustGetPort("http")
85+
output["builder-hub-proxy"] = fmt.Sprintf("http://localhost:%d", http.HostPort)
86+
}
87+
}
88+
89+
return output
90+
}

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ var artifactsAllCmd = &cobra.Command{
116116
var recipes = []internal.Recipe{
117117
&internal.L1Recipe{},
118118
&internal.OpRecipe{},
119+
&internal.BuilderNetRecipe{},
119120
}
120121

121122
func main() {

0 commit comments

Comments
 (0)