Skip to content

Commit f036689

Browse files
TymKhCopilot
andauthored
Separate builders by networks (#50)
## 📝 Summary 1. Support multi-tenant setup split by networks 2. Admin API for builder creation now requires `network` field 3. Get Active Builders requests now returns only builders within the same network 4. Internal endpoint v1 defaults to `production` network 5. Internal endpoint v2 takes network from url param ## ⛱ Motivation and Context For ease of setup of staging networks which should not share orderflow with the main builderNet env ## 📚 References --- ## ✅ I have run these commands * [x] `make lint` * [x] `make test` * [x] `go mod tidy` --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 16b4754 commit f036689

File tree

13 files changed

+180
-27
lines changed

13 files changed

+180
-27
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docker/httpserver/Dockerfile

adapters/database/service.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func (s *Service) GetActiveConfigForBuilder(ctx context.Context, builderName str
148148
return config.Config, err
149149
}
150150

151-
func (s *Service) GetActiveBuildersWithServiceCredentials(ctx context.Context) ([]domain.BuilderWithServices, error) {
151+
func (s *Service) GetActiveBuildersWithServiceCredentials(ctx context.Context, network string) ([]domain.BuilderWithServices, error) {
152152
rows, err := s.DB.QueryContext(ctx, `
153153
SELECT
154154
b.name,
@@ -161,10 +161,10 @@ func (s *Service) GetActiveBuildersWithServiceCredentials(ctx context.Context) (
161161
LEFT JOIN
162162
service_credential_registrations scr ON b.name = scr.builder_name
163163
WHERE
164-
b.is_active = true AND (scr.is_active = true OR scr.is_active IS NULL)
164+
b.is_active = true AND (scr.is_active = true OR scr.is_active IS NULL) AND b.network = $1
165165
ORDER BY
166166
b.name, scr.service
167-
`)
167+
`, network)
168168
if err != nil {
169169
return nil, err
170170
}
@@ -256,9 +256,9 @@ func (s *Service) AddBuilder(ctx context.Context, builder domain.Builder) error
256256
return err
257257
}
258258
_, err = s.DB.ExecContext(ctx, `
259-
INSERT INTO builders (name, ip_address, is_active)
260-
VALUES ($1, $2, $3)
261-
`, builder.Name, bIP, builder.IsActive)
259+
INSERT INTO builders (name, ip_address, is_active, network)
260+
VALUES ($1, $2, $3, $4)
261+
`, builder.Name, bIP, builder.IsActive, builder.Network)
262262
return err
263263
}
264264

adapters/database/service_test.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,21 @@ func TestGetBuilder(t *testing.T) {
1919
if err != nil {
2020
t.Errorf("NewDatabaseService() = %v; want nil", err)
2121
}
22+
_, err = serv.DB.Exec("TRUNCATE TABLE public.builders CASCADE")
23+
require.NoError(t, err)
24+
_, err = serv.DB.Exec("TRUNCATE TABLE public.measurements_whitelist CASCADE")
25+
require.NoError(t, err)
26+
2227
t.Run("GetBuilder2", func(t *testing.T) {
2328
t.Run("should return a builder", func(t *testing.T) {
24-
_, err := serv.DB.Exec("INSERT INTO public.builders (name, ip_address, is_active, created_at, updated_at) VALUES ('flashbots-builder', '192.168.1.1', true, '2024-10-11 13:05:56.845615 +00:00', '2024-10-11 13:05:56.845615 +00:00');")
29+
_, err := serv.DB.Exec("INSERT INTO public.builders (name, ip_address, is_active, created_at, updated_at, network) VALUES ('flashbots-builder', '192.168.1.1', true, '2024-10-11 13:05:56.845615 +00:00', '2024-10-11 13:05:56.845615 +00:00', 'production');")
2530
require.NoError(t, err)
2631
whitelist, err := serv.GetBuilderByIP(net.ParseIP("192.168.1.1"))
2732
require.NoError(t, err)
2833
require.Equal(t, whitelist.Name, "flashbots-builder")
2934
})
3035
t.Run("get all active builders", func(t *testing.T) {
31-
whitelist, err := serv.GetActiveBuildersWithServiceCredentials(context.Background())
36+
whitelist, err := serv.GetActiveBuildersWithServiceCredentials(context.Background(), domain.ProductionNetwork)
3237
require.Nil(t, err)
3338
require.Lenf(t, whitelist, 1, "expected 1 builder, got %d", len(whitelist))
3439
require.Equal(t, whitelist[0].Builder.Name, "flashbots-builder")
@@ -44,9 +49,9 @@ func TestGetBuilder(t *testing.T) {
4449
}
4550

4651
func TestAdminFlow(t *testing.T) {
47-
//if os.Getenv("RUN_DB_TESTS") != "1" {
48-
// t.Skip("skipping test; RUN_DB_TESTS is not set to 1")
49-
//}
52+
if os.Getenv("RUN_DB_TESTS") != "1" {
53+
t.Skip("skipping test; RUN_DB_TESTS is not set to 1")
54+
}
5055
dbService, err := NewDatabaseService("postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable")
5156
if err != nil {
5257
t.Errorf("NewDatabaseService() = %v; want nil", err)
@@ -65,6 +70,7 @@ func TestAdminFlow(t *testing.T) {
6570
builder := domain.Builder{
6671
Name: "test-builder",
6772
IPAddress: net.ParseIP("127.0.0.1"),
73+
Network: domain.ProductionNetwork,
6874
IsActive: false,
6975
}
7076
err := dbService.AddBuilder(context.Background(), builder)
@@ -76,7 +82,7 @@ func TestAdminFlow(t *testing.T) {
7682
require.NoError(t, err)
7783
})
7884
t.Run("get builders", func(t *testing.T) {
79-
builders, err := dbService.GetActiveBuildersWithServiceCredentials(context.Background())
85+
builders, err := dbService.GetActiveBuildersWithServiceCredentials(context.Background(), "")
8086
require.NoError(t, err)
8187
require.Len(t, builders, 0)
8288
})
@@ -89,7 +95,7 @@ func TestAdminFlow(t *testing.T) {
8995
require.NoError(t, err)
9096
})
9197
t.Run("get builders", func(t *testing.T) {
92-
builders, err := dbService.GetActiveBuildersWithServiceCredentials(context.Background())
98+
builders, err := dbService.GetActiveBuildersWithServiceCredentials(context.Background(), domain.ProductionNetwork)
9399
require.NoError(t, err)
94100
require.Len(t, builders, 1)
95101
require.Equal(t, builders[0].Builder.Name, "test-builder")

adapters/database/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type Builder struct {
4444
Name string `db:"name"`
4545
IPAddress pgtype.Inet `db:"ip_address"`
4646
IsActive bool `db:"is_active"`
47+
Network string `db:"network"`
4748
CreatedAt time.Time `db:"created_at"`
4849
UpdatedAt time.Time `db:"updated_at"`
4950
DeprecatedAt *time.Time `db:"deprecated_at"`
@@ -57,6 +58,7 @@ func convertBuilderToDomain(builder Builder) (*domain.Builder, error) {
5758
Name: builder.Name,
5859
IPAddress: builder.IPAddress.IPNet.IP,
5960
IsActive: builder.IsActive,
61+
Network: builder.Network,
6062
}, nil
6163
}
6264

application/service.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
type BuilderDataAccessor interface {
1313
GetActiveMeasurements(ctx context.Context) ([]domain.Measurement, error)
14-
GetActiveBuildersWithServiceCredentials(ctx context.Context) ([]domain.BuilderWithServices, error)
14+
GetActiveBuildersWithServiceCredentials(ctx context.Context, network string) ([]domain.BuilderWithServices, error)
1515
GetActiveMeasurementsByType(ctx context.Context, attestationType string) ([]domain.Measurement, error)
1616
GetBuilderByIP(ip net.IP) (*domain.Builder, error)
1717
GetActiveConfigForBuilder(ctx context.Context, builderName string) (json.RawMessage, error)
@@ -36,8 +36,8 @@ func (b *BuilderHub) GetAllowedMeasurements(ctx context.Context) ([]domain.Measu
3636
return b.dataAccessor.GetActiveMeasurements(ctx)
3737
}
3838

39-
func (b *BuilderHub) GetActiveBuilders(ctx context.Context) ([]domain.BuilderWithServices, error) {
40-
return b.dataAccessor.GetActiveBuildersWithServiceCredentials(ctx)
39+
func (b *BuilderHub) GetActiveBuilders(ctx context.Context, network string) ([]domain.BuilderWithServices, error) {
40+
return b.dataAccessor.GetActiveBuildersWithServiceCredentials(ctx, network)
4141
}
4242

4343
func (b *BuilderHub) LogEvent(ctx context.Context, eventName, builderName, name string) error {

docs/api-docs/admin-api/Add new builder.bru

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ post {
1313
body:json {
1414
{
1515
"name": "{builder}",
16-
"ip_address": "{ip_address}"
16+
"ip_address": "{ip_address}",
17+
"network": "{network}"
1718
}
1819
}

domain/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ var (
1414
ErrInvalidMeasurement = errors.New("no such active measurement found")
1515
)
1616

17+
const ProductionNetwork = "production"
18+
1719
const EventGetConfig = "GetConfig"
1820

1921
type Measurement struct {
@@ -38,6 +40,7 @@ type Builder struct {
3840
Name string `json:"name"`
3941
IPAddress net.IP `json:"ip_address"`
4042
IsActive bool `json:"is_active"`
43+
Network string `json:"network"`
4144
}
4245

4346
type BuilderWithServices struct {

httpserver/e2e_test.go

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"net/http/httptest"
1010
"os"
11+
"slices"
1112
"testing"
1213

1314
"github.com/ethereum/go-ethereum/common"
@@ -24,9 +25,105 @@ func TestCreateMultipleBuilders(t *testing.T) {
2425
}
2526
s, _, _ := createServer(t)
2627

27-
createBuilder(t, s, "test-builder-1", "127.0.0.1")
28-
createBuilder(t, s, "test-builder-2", "127.0.0.2")
29-
createBuilder(t, s, "test-builder-3", "127.0.0.3")
28+
createBuilder(t, s, "test-builder-1", "127.0.0.1", domain.ProductionNetwork)
29+
createBuilder(t, s, "test-builder-2", "127.0.0.2", domain.ProductionNetwork)
30+
createBuilder(t, s, "test-builder-3", "127.0.0.3", domain.ProductionNetwork)
31+
}
32+
33+
func TestCreateMultipleNetworkBuilders(t *testing.T) {
34+
if os.Getenv("RUN_DB_TESTS") != "1" {
35+
t.Skip("skipping test; RUN_DB_TESTS is not set to 1")
36+
}
37+
s, _, _ := createServer(t)
38+
39+
createBuilder(t, s, "production-1", "127.0.0.1", domain.ProductionNetwork)
40+
createBuilder(t, s, "production-2", "127.0.0.2", domain.ProductionNetwork)
41+
createBuilder(t, s, "production-3", "127.0.0.3", domain.ProductionNetwork)
42+
43+
createBuilder(t, s, "test-1", "127.0.1.1", "test-network")
44+
createBuilder(t, s, "test-2", "127.0.1.2", "test-network")
45+
createBuilder(t, s, "test-3", "127.0.1.3", "test-network")
46+
createBuilder(t, s, "test-4", "127.0.1.4", "test-network")
47+
48+
measurement := ports.Measurement{
49+
Name: "test-measurement-1",
50+
AttestationType: "test-attestation-type-1",
51+
Measurements: map[string]domain.SingleMeasurement{
52+
"8": {
53+
Expected: "0000000000000000000000000000000000000000000000000000000000000000",
54+
},
55+
"11": {
56+
Expected: "efa43e0beff151b0f251c4abf48152382b1452b4414dbd737b4127de05ca31f7",
57+
},
58+
},
59+
}
60+
createMeasurement(t, s, measurement)
61+
62+
t.Run("NoAuthV1", func(t *testing.T) {
63+
var resp []ports.BuilderWithServiceCreds
64+
sc, _ := execRequestNoAuth(t, s.GetInternalRouter(), http.MethodGet, "/api/internal/l1-builder/v1/builders", nil, &resp)
65+
require.Equal(t, http.StatusOK, sc)
66+
require.Len(t, resp, 3)
67+
validNames := []string{"production-1", "production-2", "production-3"}
68+
for _, b := range resp {
69+
if !slices.Contains(validNames, b.Name) {
70+
require.Fail(t, "Builder not found")
71+
}
72+
}
73+
})
74+
75+
t.Run("NoAuthV2 Networked", func(t *testing.T) {
76+
var resp []ports.BuilderWithServiceCreds
77+
sc, _ := execRequestNoAuth(t, s.GetInternalRouter(), http.MethodGet, fmt.Sprintf("/api/internal/l1-builder/v2/network/%s/builders", domain.ProductionNetwork), nil, &resp)
78+
require.Equal(t, http.StatusOK, sc)
79+
require.Len(t, resp, 3)
80+
validNames := []string{"production-1", "production-2", "production-3"}
81+
for _, b := range resp {
82+
if !slices.Contains(validNames, b.Name) {
83+
require.Fail(t, "Builder not found")
84+
}
85+
}
86+
})
87+
88+
t.Run("NoAuthV2 Networked other network", func(t *testing.T) {
89+
var resp []ports.BuilderWithServiceCreds
90+
sc, _ := execRequestNoAuth(t, s.GetInternalRouter(), http.MethodGet, fmt.Sprintf("/api/internal/l1-builder/v2/network/%s/builders", "test-network"), nil, &resp)
91+
require.Equal(t, http.StatusOK, sc)
92+
require.Len(t, resp, 4)
93+
validNames := []string{"test-1", "test-2", "test-3", "test-4"}
94+
for _, b := range resp {
95+
if !slices.Contains(validNames, b.Name) {
96+
require.Fail(t, "Builder not found")
97+
}
98+
}
99+
})
100+
101+
t.Run("Auth active builders prod", func(t *testing.T) {
102+
resp := make([]ports.BuilderWithServiceCreds, 0)
103+
status, _ := execRequestAuth(t, s.GetRouter(), http.MethodGet, "/api/l1-builder/v1/builders", nil, &resp, measurement.AttestationType, map[string]string{"8": "0000000000000000000000000000000000000000000000000000000000000000", "11": "efa43e0beff151b0f251c4abf48152382b1452b4414dbd737b4127de05ca31f7"}, "127.0.0.1")
104+
require.Equal(t, http.StatusOK, status)
105+
require.Len(t, resp, 3)
106+
validNames := []string{"production-1", "production-2", "production-3"}
107+
for _, b := range resp {
108+
if !slices.Contains(validNames, b.Name) {
109+
require.Fail(t, "Builder not found")
110+
}
111+
}
112+
})
113+
114+
t.Run("Auth active builders other network", func(t *testing.T) {
115+
resp := make([]ports.BuilderWithServiceCreds, 0)
116+
status, _ := execRequestAuth(t, s.GetRouter(), http.MethodGet, "/api/l1-builder/v1/builders", nil, &resp, measurement.AttestationType, map[string]string{"8": "0000000000000000000000000000000000000000000000000000000000000000", "11": "efa43e0beff151b0f251c4abf48152382b1452b4414dbd737b4127de05ca31f7"}, "127.0.1.1")
117+
require.Equal(t, http.StatusOK, status)
118+
require.Len(t, resp, 4)
119+
validNames := []string{"test-1", "test-2", "test-3", "test-4"}
120+
for _, b := range resp {
121+
if !slices.Contains(validNames, b.Name) {
122+
require.Fail(t, "Builder not found")
123+
}
124+
}
125+
})
126+
30127
}
31128

32129
func TestCreateMultipleMeasurements(t *testing.T) {
@@ -70,7 +167,7 @@ func TestAuthInteractionFlow(t *testing.T) {
70167

71168
builderName := "test_builder_1"
72169
ip := "127.0.0.1"
73-
createBuilder(t, s, builderName, ip)
170+
createBuilder(t, s, builderName, ip, domain.ProductionNetwork)
74171
measurement := ports.Measurement{
75172
Name: "test-measurement-1",
76173
AttestationType: "test-attestation-type-1",
@@ -131,10 +228,11 @@ func TestAuthInteractionFlow(t *testing.T) {
131228
}
132229

133230
// createBuilder emulates admin flow to provision a builder
134-
func createBuilder(t *testing.T, s *Server, builderName, ip string) {
231+
func createBuilder(t *testing.T, s *Server, builderName, ip, network string) {
135232
builder := ports.Builder{
136233
Name: builderName,
137234
IPAddress: ip,
235+
Network: network,
138236
}
139237
t.Run("CreateBuilder", func(t *testing.T) {
140238
sc, _ := execRequestNoAuth(t, s.GetAdminRouter(), http.MethodPost, "/api/admin/v1/builders", builder, nil)
@@ -161,7 +259,7 @@ func createBuilder(t *testing.T, s *Server, builderName, ip string) {
161259
t.Run("CheckBuilder", func(t *testing.T) {
162260
// Check if the builder is created
163261
var resp []ports.BuilderWithServiceCreds
164-
sc, _ := execRequestNoAuth(t, s.GetInternalRouter(), http.MethodGet, "/api/internal/l1-builder/v1/builders", nil, &resp)
262+
sc, _ := execRequestNoAuth(t, s.GetInternalRouter(), http.MethodGet, fmt.Sprintf("/api/internal/l1-builder/v2/network/%s/builders", network), nil, &resp)
165263
require.Equal(t, http.StatusOK, sc)
166264
// require.Len(t, resp, 1)
167265
for _, b := range resp {

httpserver/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func (srv *Server) GetRouter() http.Handler {
9797
mux.Get("/api/l1-builder/v1/builders", srv.appHandler.GetActiveBuilders)
9898
mux.Post("/api/l1-builder/v1/register_credentials/{service}", srv.appHandler.RegisterCredentials)
9999
mux.Get("/api/internal/l1-builder/v1/builders", srv.appHandler.GetActiveBuildersNoAuth)
100+
mux.Get("/api/internal/l1-builder/v2/network/{network}/builders", srv.appHandler.GetActiveBuildersNoAuthNetworked)
100101
if srv.cfg.EnablePprof {
101102
srv.log.Info("pprof API enabled")
102103
mux.Mount("/debug", middleware.Profiler())
@@ -133,6 +134,7 @@ func (srv *Server) GetInternalRouter() http.Handler {
133134

134135
mux.Get("/api/l1-builder/v1/measurements", srv.appHandler.GetAllowedMeasurements)
135136
mux.Get("/api/internal/l1-builder/v1/builders", srv.appHandler.GetActiveBuildersNoAuth)
137+
mux.Get("/api/internal/l1-builder/v2/network/{network}/builders", srv.appHandler.GetActiveBuildersNoAuthNetworked)
136138

137139
return mux
138140
}

ports/admin_handler.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ func (s *AdminHandler) AddBuilder(w http.ResponseWriter, r *http.Request) {
125125
w.WriteHeader(http.StatusBadRequest)
126126
return
127127
}
128+
if builder.Network == "" {
129+
s.log.Error("network field is required")
130+
w.WriteHeader(http.StatusBadRequest)
131+
_, _ = w.Write([]byte("network field is required"))
132+
return
133+
}
128134
dBuilder, err := toDomainBuilder(builder, false)
129135
if err != nil {
130136
s.log.Error("Failed to convert builder to domain builder", "err", err)

0 commit comments

Comments
 (0)