Skip to content
Open
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
5 changes: 5 additions & 0 deletions server/get_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ func (m *BoostService) getHeader(log *logrus.Entry, slot phase0.Slot, pubkey, pa

// Request a bid from each relay
for _, relayConfig := range relayConfigs {
if m.relayIsBlacklisted(relayConfig.RelayEntry) {
log.WithField("relay", relayConfig.RelayEntry.String()).Warn("skipping temporarily blacklisted relay")
continue
}

wg.Add(1)
go func(relayConfig types.RelayConfig) {
relay := relayConfig.RelayEntry
Expand Down
46 changes: 37 additions & 9 deletions server/get_payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"io"
"mime"
"net/http"
"slices"
"strconv"
"sync/atomic"
"time"
Expand Down Expand Up @@ -56,6 +55,7 @@ const (
type payloadResult struct {
success bool
response *builderApi.VersionedSubmitBlindedBlockResponse
relay types.RelayEntry
}

// Deprecated: For reference: https://github.com/ethereum/builder-specs/issues/119
Expand Down Expand Up @@ -163,6 +163,9 @@ func (m *BoostService) innerGetPayload(log *logrus.Entry, signedBlindedBeaconBlo

for _, relayConfig := range m.relayConfigs {
go func(relay types.RelayEntry, versionToUse GetPayloadVersion) {
matchedRelay, relayShouldDeliver := findRelayForPayload(relay, originalBid.relays)
delivered := false

var url string
if versionToUse == GetPayloadV1 {
url = relay.GetURI(params.PathGetPayload)
Expand All @@ -171,6 +174,18 @@ func (m *BoostService) innerGetPayload(log *logrus.Entry, signedBlindedBeaconBlo
}
innerLog := log.WithField("url", url)

defer func() {
if !relayShouldDeliver {
return
}
if delivered {
m.clearRelayFailure(relay)
return
}
innerLog.Warn("temporarily blacklisting relay after payload withholding")
m.markRelayFailure(relay)
}()

// If the request fails, try again a few times with 100ms between tries
resp, err := retry(requestCtx, m.requestMaxRetries, 100*time.Millisecond, func() (*http.Response, error) {
innerLog = innerLog.WithField("url", url)
Expand All @@ -179,13 +194,7 @@ func (m *BoostService) innerGetPayload(log *logrus.Entry, signedBlindedBeaconBlo
requestBytes := signedBlindedBeaconBlockBytes

// Check if the relay supports SSZ
relaySupportsSSZ := false
for _, originalBidRelay := range originalBid.relays {
if relay.URL == originalBidRelay.URL {
relaySupportsSSZ = originalBidRelay.SupportsSSZ
break
}
}
relaySupportsSSZ := matchedRelay.SupportsSSZ
innerLog.WithField("relaySupportsSSZ", relaySupportsSSZ).Debug("encoding preference")

// If the relay provided the bid in JSON or did not provide a bid for this payload,
Expand All @@ -199,7 +208,7 @@ func (m *BoostService) innerGetPayload(log *logrus.Entry, signedBlindedBeaconBlo
return nil, err
}
innerLog.WithFields(logrus.Fields{
"relayProvidedBid": slices.Contains(originalBid.relays, relay),
"relayProvidedBid": relayShouldDeliver,
"conversionTime": time.Since(startTime),
}).Info("Converted request from SSZ to JSON for relay")
}
Expand Down Expand Up @@ -278,6 +287,7 @@ func (m *BoostService) innerGetPayload(log *logrus.Entry, signedBlindedBeaconBlo

var result payloadResult
result.success = true
result.relay = relay

if versionToUse == GetPayloadV1 {
// Get the resp body content
Expand Down Expand Up @@ -317,6 +327,8 @@ func (m *BoostService) innerGetPayload(log *logrus.Entry, signedBlindedBeaconBlo
result.response = response
}

delivered = true

// We have received a valid response, return the first one.
// The other requests will be running in the background to provide redundancy
// in case the relay provider which returned the first request fails to broadcast the block.
Expand Down Expand Up @@ -711,6 +723,22 @@ func bidKey(slot phase0.Slot, blockHash phase0.Hash32) string {
return fmt.Sprintf("%v%v", slot, blockHash)
}

func findRelayForPayload(relay types.RelayEntry, relays []types.RelayEntry) (types.RelayEntry, bool) {
for _, candidate := range relays {
if relayEntriesEqual(relay, candidate) {
return candidate, true
}
}
return types.RelayEntry{}, false
}

func relayEntriesEqual(a, b types.RelayEntry) bool {
if a.URL != nil && b.URL != nil {
return a.URL.String() == b.URL.String()
}
return a.PublicKey == b.PublicKey
}

// retry executes the provided function until it succeeds, the context is done, or
// the maximum number of attempts is reached. It waits for 'delay' between attempts.
func retry(ctx context.Context, maxAttempts int, delay time.Duration, fn func() (*http.Response, error)) (*http.Response, error) {
Expand Down
48 changes: 48 additions & 0 deletions server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ var (
nilResponse = struct{}{}
)

const relayBlacklistDuration = 30 * time.Second

type httpErrorResp struct {
Code int `json:"code"`
Message string `json:"message"`
Expand Down Expand Up @@ -100,6 +102,9 @@ type BoostService struct {
relayConfigsLock sync.RWMutex

metricsAddr string

relayBlacklist map[string]time.Time
relayBlacklistLock sync.Mutex
}

// NewBoostService created a new BoostService
Expand Down Expand Up @@ -140,6 +145,7 @@ func NewBoostService(opts BoostServiceOpts) (*BoostService, error) {
requestMaxRetries: opts.RequestMaxRetries,
timeoutGetHeaderMs: opts.TimeoutGetHeaderMs,
lateInSlotTimeMs: opts.LateInSlotTimeMs,
relayBlacklist: make(map[string]time.Time),
}, nil
}

Expand Down Expand Up @@ -177,6 +183,48 @@ func (m *BoostService) getRouter() http.Handler {
return loggedRouter
}

func (m *BoostService) relayIsBlacklisted(relay types.RelayEntry) bool {
if relay.URL == nil {
return false
}

key := relay.URL.String()
m.relayBlacklistLock.Lock()
defer m.relayBlacklistLock.Unlock()

lastFailure, ok := m.relayBlacklist[key]
if !ok {
return false
}

if time.Since(lastFailure) > relayBlacklistDuration {
delete(m.relayBlacklist, key)
return false
}

return true
}

func (m *BoostService) markRelayFailure(relay types.RelayEntry) {
if relay.URL == nil {
return
}

m.relayBlacklistLock.Lock()
m.relayBlacklist[relay.URL.String()] = time.Now()
m.relayBlacklistLock.Unlock()
}

func (m *BoostService) clearRelayFailure(relay types.RelayEntry) {
if relay.URL == nil {
return
}

m.relayBlacklistLock.Lock()
delete(m.relayBlacklist, relay.URL.String())
m.relayBlacklistLock.Unlock()
}

// StartHTTPServer starts the HTTP server for this boost service instance
func (m *BoostService) StartHTTPServer() error {
if m.srv != nil {
Expand Down