diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f14fdde..1e9a95e 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -10,6 +10,21 @@ jobs: test: name: Test runs-on: ubuntu-latest + services: + # Label used to access the service container + postgres: + # Docker Hub image + image: postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: - name: Set up Go uses: actions/setup-go@v3 @@ -20,8 +35,11 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v2 + - name: Run migrations + run: for file in schema/*.sql; do psql "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" -f $file; done + - name: Run unit tests and generate the coverage report - run: make test-race + run: RUN_DB_TESTS=1 make test-race lint: name: Lint @@ -30,7 +48,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: ^1.21 + go-version: ^1.22 id: go - name: Check out code into the Go module directory @@ -40,7 +58,7 @@ jobs: run: go install mvdan.cc/gofumpt@v0.4.0 - name: Install staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@v0.4.2 + run: go install honnef.co/go/tools/cmd/staticcheck@2024.1.1 - name: Install golangci-lint run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 diff --git a/.golangci.yaml b/.golangci.yaml index 2dd2c60..5dcb09b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -31,6 +31,7 @@ linters: - prealloc - whitespace - musttag + - mnd # # Disabled because of generics: diff --git a/README.md b/README.md index 95f2f09..8d4384d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -# BuilderConfigHub +# BuilderHub [![Test status](https://github.com/flashbots/builder-hub/actions/workflows/checks.yml/badge.svg?branch=main)](https://github.com/flashbots/builder-hub/actions?query=workflow%3A%22Checks%22) -Endpoint for TDX builders to talk to. - -https://www.notion.so/flashbots/BuilderConfigHub-1076b4a0d8768074bcdcd1c06c26ec87 +Contains code for builder hub service that acts as a data source for builders registration and configuration. +Docs: https://buildernet.github.io/docs/ --- ## Getting started @@ -49,6 +48,8 @@ make fmt # start the database docker run -d --name postgres-test -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres postgres +for file in schema/*.sql; do psql "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" -f $file; done + # run the tests RUN_DB_TESTS=1 make test diff --git a/adapters/database/service_test.go b/adapters/database/service_test.go index 7acca77..505f54f 100644 --- a/adapters/database/service_test.go +++ b/adapters/database/service_test.go @@ -2,37 +2,41 @@ package database import ( "context" - "fmt" "net" + "os" "testing" + + "github.com/stretchr/testify/require" ) func TestGetBuilder(t *testing.T) { + if os.Getenv("RUN_DB_TESTS") != "1" { + t.Skip("skipping test; RUN_DB_TESTS is not set to 1") + } serv, err := NewDatabaseService("postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable") if err != nil { t.Errorf("NewDatabaseService() = %v; want nil", err) } t.Run("GetBuilder2", func(t *testing.T) { t.Run("should return a builder", func(t *testing.T) { + _, 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');") + require.NoError(t, err) whitelist, err := serv.GetBuilderByIP(net.ParseIP("192.168.1.1")) - if err != nil { - t.Errorf("GetIPWhitelistByIP() = %v; want nil", err) - } - fmt.Println(whitelist) + require.NoError(t, err) + require.Equal(t, whitelist.Name, "flashbots-builder") }) t.Run("get all active builders", func(t *testing.T) { whitelist, err := serv.GetActiveBuildersWithServiceCredentials(context.Background()) - if err != nil { - t.Errorf("GetIPWhitelistByIP() = %v; want nil", err) - } - fmt.Println(whitelist) + require.Nil(t, err) + require.Lenf(t, whitelist, 1, "expected 1 builder, got %d", len(whitelist)) + require.Equal(t, whitelist[0].Builder.Name, "flashbots-builder") }) t.Run("get all active measurements", func(t *testing.T) { whitelist, err := serv.GetActiveMeasurements(context.Background()) if err != nil { t.Errorf("GetIPWhitelistByIP() = %v; want nil", err) } - fmt.Println(whitelist) + require.Len(t, whitelist, 0) }) }) } diff --git a/httpserver/handler.go b/httpserver/handler.go index b92d1cc..8f3d007 100644 --- a/httpserver/handler.go +++ b/httpserver/handler.go @@ -39,7 +39,3 @@ func (srv *Server) handleTestPanic(w http.ResponseWriter, r *http.Request) { panic("foo") // w.WriteHeader(http.StatusOK) } - -// -// BuilderConfigHub API: https://www.notion.so/flashbots/BuilderConfigHub-1076b4a0d8768074bcdcd1c06c26ec87?pvs=4#10a6b4a0d87680fd81e0cad9bac3b8c5 -// diff --git a/httpserver/handler_test.go b/httpserver/handler_test.go index 6408261..0ccdbb3 100644 --- a/httpserver/handler_test.go +++ b/httpserver/handler_test.go @@ -1,7 +1,6 @@ package httpserver import ( - "bytes" "io" "net/http" "net/http/httptest" @@ -17,6 +16,7 @@ import ( var testServerConfig = &HTTPServerConfig{ Log: getTestLogger(), } +var _ = testServerConfig func getTestLogger() *httplog.Logger { return common.SetupLogger(&common.LoggingOpts{ @@ -99,33 +99,3 @@ func Test_Handlers_Healthcheck_Drain_Undrain(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode, "Healthcheck must return `Ok` after undraining") } } - -func Test_Handlers_BuilderConfigHub(t *testing.T) { - routes := []struct { - method string - path string - payload []byte - }{ - // BuilderConfigHub API: https://www.notion.so/flashbots/BuilderConfigHub-1076b4a0d8768074bcdcd1c06c26ec87?pvs=4#10a6b4a0d87680fd81e0cad9bac3b8c5 - {http.MethodGet, "/api/l1-builder/v1/measurements", nil}, - {http.MethodGet, "/api/l1-builder/v1/configuration", nil}, - {http.MethodGet, "/api/l1-builder/v1/builders", nil}, - {http.MethodPost, "/api/l1-builder/v1/register_credentials/rbuilder", []byte(`{"var1":"foo"}`)}, - } - - srv, err := NewHTTPServer(testServerConfig, ports.NewBuilderHubHandler(nil, getTestLogger())) - require.NoError(t, err) - - for _, r := range routes { - var payload io.Reader - if r.payload != nil { - payload = bytes.NewReader(r.payload) - } - req, err := http.NewRequest(r.method, r.path, payload) - require.NoError(t, err) - - rr := httptest.NewRecorder() - srv.getRouter().ServeHTTP(rr, req) - require.Equal(t, http.StatusOK, rr.Code) - } -} diff --git a/httpserver/server.go b/httpserver/server.go index 099de0f..452cc7e 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -66,6 +66,7 @@ func (srv *Server) getRouter() http.Handler { mux.Use(httplog.RequestLogger(srv.log)) mux.Use(middleware.Recoverer) + mux.Use(metrics.Middleware) // System API mux.Get("/livez", srv.handleLivenessCheck) @@ -76,13 +77,11 @@ func (srv *Server) getRouter() http.Handler { // Dev mux.Get("/test-panic", srv.handleTestPanic) - // BuilderConfigHub API: https://www.notion.so/flashbots/BuilderConfigHub-1076b4a0d8768074bcdcd1c06c26ec87?pvs=4#10a6b4a0d87680fd81e0cad9bac3b8c5 mux.Get("/api/l1-builder/v1/measurements", srv.appHandler.GetAllowedMeasurements) mux.Get("/api/l1-builder/v1/configuration", srv.appHandler.GetConfigSecrets) mux.Get("/api/l1-builder/v1/builders", srv.appHandler.GetActiveBuilders) mux.Post("/api/l1-builder/v1/register_credentials/{service}", srv.appHandler.RegisterCredentials) mux.Get("/api/internal/l1-builder/v1/builders", srv.appHandler.GetActiveBuildersNoAuth) - if srv.cfg.EnablePprof { srv.log.Info("pprof API enabled") mux.Mount("/debug", middleware.Profiler()) diff --git a/metrics/metrics.go b/metrics/metrics.go index 648d8a2..89ce020 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -2,22 +2,14 @@ package metrics import ( + "fmt" + "github.com/VictoriaMetrics/metrics" ) -var testMetric = metrics.NewCounter("test1") - -// sbundleProcessDurationSummary = metrics.NewSummary("sbundle_process_duration_milliseconds") - -// const ( -// sbundleRPCCallDurationLabel = `sbundle_rpc_call_duration_milliseconds{method="%s"}` -// sbundleRPCCallErrorCounterLabel = `sbundle_rpc_call_error_total{method="%s"}` - -// sbundleSentToBuilderLabel = `bundle_sent_to_builder_total{builder="%s"}` -// sbundleSentToBuilderFailureLabel = `bundle_sent_to_builder_failure_total{builder="%s"}` -// sbundleSentToBuilderDurationSummaryLabel = `bundle_sent_to_builder_duration_milliseconds{builder="%s"}` -// ) +const requestDurationLabel = `http_server_request_duration_milliseconds{route="%s"}` -func IncSbundlesReceived() { - testMetric.Inc() +func recordRequestDuration(route string, duration int64) { + l := fmt.Sprintf(requestDurationLabel, route) + metrics.GetOrCreateSummary(l).Update(float64(duration)) } diff --git a/metrics/middleware.go b/metrics/middleware.go new file mode 100644 index 0000000..d44a069 --- /dev/null +++ b/metrics/middleware.go @@ -0,0 +1,21 @@ +package metrics + +import ( + "net/http" + "time" + + "github.com/go-chi/chi/v5" +) + +func Middleware(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + startAt := time.Now() + defer func() { + routePattern := chi.RouteContext(r.Context()).RoutePattern() + recordRequestDuration(routePattern, time.Since(startAt).Milliseconds()) + }() + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} diff --git a/metrics/server.go b/metrics/server.go index 18cbb2e..46d8799 100644 --- a/metrics/server.go +++ b/metrics/server.go @@ -47,6 +47,7 @@ func (srv *MetricsServer) getRouter() http.Handler { } mux.Use(middleware.Recoverer) + mux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) { metrics.WritePrometheus(w, true) })