From e60b001efd90121369cf3f6760aee36d833b47ae Mon Sep 17 00:00:00 2001 From: 0xWeakSheep Date: Mon, 5 Jan 2026 21:45:51 +0800 Subject: [PATCH] add black list control --- server/get_header.go | 5 +++++ server/get_payload.go | 46 +++++++++++++++++++++++++++++++++-------- server/service.go | 48 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 9 deletions(-) diff --git a/server/get_header.go b/server/get_header.go index 85113406..7744c161 100644 --- a/server/get_header.go +++ b/server/get_header.go @@ -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 diff --git a/server/get_payload.go b/server/get_payload.go index 5f2c6e2c..72ee5a70 100644 --- a/server/get_payload.go +++ b/server/get_payload.go @@ -9,7 +9,6 @@ import ( "io" "mime" "net/http" - "slices" "strconv" "sync/atomic" "time" @@ -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 @@ -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) @@ -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) @@ -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, @@ -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") } @@ -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 @@ -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. @@ -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) { diff --git a/server/service.go b/server/service.go index 4c8c24cf..1e69befb 100644 --- a/server/service.go +++ b/server/service.go @@ -41,6 +41,8 @@ var ( nilResponse = struct{}{} ) +const relayBlacklistDuration = 30 * time.Second + type httpErrorResp struct { Code int `json:"code"` Message string `json:"message"` @@ -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 @@ -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 } @@ -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 {