Skip to content
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

initial implementation #4

Merged
merged 6 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# IDE
.vscode

.idea
# Builds
/build
.aider*
29 changes: 29 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1
FROM golang:1.23-alpine AS builder
ARG VERSION
RUN apk add --no-cache gcc sqlite-dev musl-dev
WORKDIR /build
# First only add go.mod and go.sum, then run go mod download to cache dependencies
# in a separate layer.
ADD go.mod go.sum /build/
RUN --mount=type=cache,target=/root/.cache/go-build go mod download
# Now add the rest of the source code and build the application.
ADD . /build/
RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=1 GOOS=linux \
go build \
-trimpath \
-ldflags "-s -X github.com/flashbots/builder-hub/common.Version=${VERSION} -w -extldflags \"-static\"" \
-v \
-o builder-hub \
cmd/httpserver/main.go

FROM alpine:latest
RUN apk update && apk upgrade
RUN apk add --no-cache sqlite-dev
# See http://stackoverflow.com/questions/34729748/installed-go-binary-not-found-in-path-on-alpine-linux-docker
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
WORKDIR /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/builder-hub /app/builder-hub
ADD testdata/ /app/testdata/
CMD ["/app/builder-hub"]
233 changes: 233 additions & 0 deletions adapters/database/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package database

import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"net"

"github.com/flashbots/builder-hub/domain"
"github.com/jackc/pgtype"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)

type Service struct {
DB *sqlx.DB
}

func NewDatabaseService(dsn string) (*Service, error) {
db, err := sqlx.Connect("postgres", dsn)
if err != nil {
return nil, err
}

db.DB.SetMaxOpenConns(50)
db.DB.SetMaxIdleConns(10)
db.DB.SetConnMaxIdleTime(0)

dbService := &Service{DB: db} //nolint:exhaustruct
return dbService, err
}

func (s *Service) Close() error {
return s.DB.Close()
}

// GetMeasurementByTypeAndHash retrieves a measurement by OID and hash
func (s *Service) GetActiveMeasurementsByType(ctx context.Context, attestationType string) ([]domain.Measurement, error) {
var measurements []Measurement
err := s.DB.SelectContext(ctx, &measurements, `SELECT * FROM measurements_whitelist WHERE is_active=true AND attestation_type=$1`, attestationType)
var domainMeasurements []domain.Measurement
for _, m := range measurements {
domainM, err := convertMeasurementToDomain(m)
if err != nil {
return nil, err
}
domainMeasurements = append(domainMeasurements, *domainM)
}
return domainMeasurements, err
}

// GetBuilderByIP retrieves a builder by IP address
func (s *Service) GetBuilderByIP(ip net.IP) (*domain.Builder, error) {
var paramIP pgtype.Inet
err := paramIP.Set(ip)
if err != nil {
return nil, err
}

var b Builder
err = s.DB.Get(&b, `
SELECT * FROM builders
WHERE ip_address = $1 and is_active = true
`, paramIP)
if errors.Is(err, sql.ErrNoRows) {
return nil, domain.ErrNotFound
}
if err != nil {
return nil, err
}
return convertBuilderToDomain(b)
}

// GetActiveMeasurements retrieves all measurements
func (s *Service) GetActiveMeasurements(ctx context.Context) ([]domain.Measurement, error) {
var measurements []Measurement
err := s.DB.SelectContext(ctx, &measurements, `SELECT * FROM measurements_whitelist WHERE is_active=true`)
var domainMeasurements []domain.Measurement
for _, m := range measurements {
domainM, err := convertMeasurementToDomain(m)
if err != nil {
return nil, err
}
domainMeasurements = append(domainMeasurements, *domainM)
}
return domainMeasurements, err
}

// RegisterCredentialsForBuilder registers new credentials for a builder, deprecating all previous credentials
// It uses hash and attestation_type to fetch the corresponding measurement_id via a subquery.
func (s *Service) RegisterCredentialsForBuilder(ctx context.Context, builderName, service, tlsCert string, ecdsaPubKey []byte, measurementName string, attestationType string) error {
// Start a transaction
tx, err := s.DB.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback() // Rollback the transaction if it's not committed

// Deprecate all previous credentials for this builder and service
_, err = tx.Exec(`
UPDATE service_credential_registrations
SET is_active = false, deprecated_at = NOW()
WHERE builder_name = $1 AND service = $2
`, builderName, service)
if err != nil {
return err
}

// Insert new credentials with a subquery to fetch the measurement_id
var nullableTLSCert sql.NullString
if tlsCert != "" {
nullableTLSCert = sql.NullString{String: tlsCert, Valid: true}
}

_, err = tx.Exec(`
INSERT INTO service_credential_registrations
(builder_name, service, tls_cert, ecdsa_pubkey, is_active, measurement_id)
VALUES ($1, $2, $3, $4, true,
(SELECT id FROM measurements_whitelist WHERE name = $5 AND attestation_type = $6)
)
`, builderName, service, nullableTLSCert, ecdsaPubKey, measurementName, attestationType)
if err != nil {
return fmt.Errorf("failed to insert credentials for builder %s: %w", builderName, err)
}

// Commit the transaction
if err = tx.Commit(); err != nil {
return err
}

return nil
}

// GetActiveConfigForBuilder retrieves the active config for a builder by name
func (s *Service) GetActiveConfigForBuilder(ctx context.Context, builderName string) (json.RawMessage, error) {
var config BuilderConfig
err := s.DB.GetContext(ctx, &config, `
SELECT * FROM builder_configs
WHERE builder_name = $1 AND is_active = true
`, builderName)
return config.Config, err
}

func (s *Service) GetActiveBuildersWithServiceCredentials(ctx context.Context) ([]domain.BuilderWithServices, error) {
rows, err := s.DB.QueryContext(ctx, `
SELECT
b.name,
b.ip_address,
scr.service,
scr.tls_cert,
scr.ecdsa_pubkey
FROM
builders b
LEFT JOIN
service_credential_registrations scr ON b.name = scr.builder_name
WHERE
b.is_active = true AND (scr.is_active = true OR scr.is_active IS NULL)
ORDER BY
b.name, scr.service
`)
if err != nil {
return nil, err
}
defer rows.Close()

buildersMap := make(map[string]*BuilderWithCredentials)

for rows.Next() {
var ipAddress pgtype.Inet
var builderName string
var service sql.NullString
var tlsCert sql.NullString
var ecdsaPubKey []byte

err := rows.Scan(&builderName, &ipAddress, &service, &tlsCert, &ecdsaPubKey)
if err != nil {
return nil, err
}

builder, exists := buildersMap[builderName]
if !exists {
builder = &BuilderWithCredentials{
Name: builderName,
IPAddress: ipAddress,
}
buildersMap[builderName] = builder
}

if service.Valid {
builder.Credentials = append(builder.Credentials, ServiceCredential{
Service: service.String,
TLSCert: tlsCert,
ECDSAPubKey: ecdsaPubKey,
})
}
}

if err = rows.Err(); err != nil {
return nil, err
}

// Convert map to slice
builders := make([]domain.BuilderWithServices, 0, len(buildersMap))
for _, builder := range buildersMap {
dBuilder, err := toDomainBuilderWithCredentials(*builder)
if err != nil {
return nil, err
}
builders = append(builders, *dBuilder)
}

return builders, nil
}

// LogEvent creates a new log entry in the event_log table.
// It uses hash and attestation_type to fetch the corresponding measurement_id via a subquery.
func (s *Service) LogEvent(ctx context.Context, eventName, builderName, name, attestationType string) error {
// Insert new event log entry with a subquery to fetch the measurement_id
_, err := s.DB.ExecContext(ctx, `
INSERT INTO event_log
(event_name, builder_name, measurement_id)
VALUES ($1, $2,
(SELECT id FROM measurements_whitelist WHERE name = $3 AND attestation_type = $4)
)
`, eventName, builderName, name, attestationType)
if err != nil {
return fmt.Errorf("failed to insert event log for builder %s: %w", builderName, err)
}

return nil
}
73 changes: 73 additions & 0 deletions adapters/database/service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package database

import (
"context"
"fmt"
"net"
"testing"
)

func TestGetBuilder(t *testing.T) {
serv, err := NewDatabaseService("postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable")
if err != nil {
t.Errorf("NewDatabaseService() = %v; want nil", err)
}
//t.Run("GetBuilder", func(t *testing.T) {
// _, err = db.Exec("create temporary table t (id serial primary key, ip inet not null);")
// if err != nil {
// log.Fatal(err)
// }
//
// var paramIP pgtype.Inet
// err = paramIP.Set("10.0.0.0/16")
// if err != nil {
// log.Fatal(err)
// }
//
// _, err = db.Exec("insert into t (ip) values ($1);", paramIP)
// if err != nil {
// log.Fatal(err)
// }
//
// var resultIP pgtype.Inet
// err = db.QueryRow("select ip from t").Scan(&resultIP)
// if err != nil {
// log.Fatal(err)
// }
//
// fmt.Println(resultIP.IPNet)
//})
//t.Run("GetBuilder", func(t *testing.T) {
// t.Run("should return a builder", func(t *testing.T) {
// builder, err := serv.GetBuilderByIP(net.ParseIP("192.168.1.100"))
// if err != nil {
// t.Errorf("GetBuilder() = %v; want nil", err)
// }
// fmt.Println(builder)
// })
//})
t.Run("GetBuilder2", func(t *testing.T) {
t.Run("should return a builder", func(t *testing.T) {
whitelist, err := serv.GetBuilderByIP(net.ParseIP("192.168.1.1"))
if err != nil {
t.Errorf("GetIPWhitelistByIP() = %v; want nil", err)
}
fmt.Println(whitelist)
})
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)
})
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)
})
})

}
Loading
Loading