Skip to content

Commit

Permalink
Merge pull request #4 from flashbots/feature/initial-implementation
Browse files Browse the repository at this point in the history
initial implementation
  • Loading branch information
TymKh authored Oct 22, 2024
2 parents 6e41160 + 96498f3 commit 701d78f
Show file tree
Hide file tree
Showing 29 changed files with 1,638 additions and 244 deletions.
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

0 comments on commit 701d78f

Please sign in to comment.