diff --git a/README.md b/README.md index 41111059..9f53bcef 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,10 @@ Usage of mev-boost: relay monitor urls - single entry or comma-separated list (scheme://host) -relays string relay urls - single entry or comma-separated list (scheme://pubkey@host) + -privileged-builder + a single privileged builder(relay pubkey), can be specified multiple times + -privileged-builders string + privileged builders(relay pubkey) - single entry or comma-separated list -request-timeout-getheader int timeout for getHeader requests to the relay [ms] (default 950) -request-timeout-getpayload int diff --git a/cli/main.go b/cli/main.go index cc16b1bb..fb258790 100644 --- a/cli/main.go +++ b/cli/main.go @@ -32,17 +32,18 @@ var ( errInvalidLoglevel = errors.New("invalid loglevel") // defaults - defaultLogJSON = os.Getenv("LOG_JSON") != "" - defaultLogLevel = common.GetEnv("LOG_LEVEL", "info") - defaultListenAddr = common.GetEnv("BOOST_LISTEN_ADDR", "localhost:18550") - defaultRelayCheck = os.Getenv("RELAY_STARTUP_CHECK") != "" - defaultRelayMinBidEth = common.GetEnvFloat64("MIN_BID_ETH", 0) - defaultDisableLogVersion = os.Getenv("DISABLE_LOG_VERSION") == "1" // disables adding the version to every log entry - defaultDebug = os.Getenv("DEBUG") != "" - defaultLogServiceTag = os.Getenv("LOG_SERVICE_TAG") - defaultRelays = os.Getenv("RELAYS") - defaultRelayMonitors = os.Getenv("RELAY_MONITORS") - defaultMaxRetries = common.GetEnvInt("REQUEST_MAX_RETRIES", 5) + defaultLogJSON = os.Getenv("LOG_JSON") != "" + defaultLogLevel = common.GetEnv("LOG_LEVEL", "info") + defaultListenAddr = common.GetEnv("BOOST_LISTEN_ADDR", "localhost:18550") + defaultRelayCheck = os.Getenv("RELAY_STARTUP_CHECK") != "" + defaultRelayMinBidEth = common.GetEnvFloat64("MIN_BID_ETH", 0) + defaultDisableLogVersion = os.Getenv("DISABLE_LOG_VERSION") == "1" // disables adding the version to every log entry + defaultDebug = os.Getenv("DEBUG") != "" + defaultLogServiceTag = os.Getenv("LOG_SERVICE_TAG") + defaultRelays = os.Getenv("RELAYS") + defaultRelayMonitors = os.Getenv("RELAY_MONITORS") + defaultMaxRetries = common.GetEnvInt("REQUEST_MAX_RETRIES", 5) + defaultPrivilegedBuilders = os.Getenv("PRIVILEGED_BUILDERS") defaultGenesisForkVersion = common.GetEnv("GENESIS_FORK_VERSION", "") defaultGenesisTime = common.GetEnvInt("GENESIS_TIMESTAMP", -1) @@ -55,8 +56,9 @@ var ( defaultTimeoutMsGetPayload = common.GetEnvInt("RELAY_TIMEOUT_MS_GETPAYLOAD", 4000) // timeout for getPayload requests defaultTimeoutMsRegisterValidator = common.GetEnvInt("RELAY_TIMEOUT_MS_REGVAL", 3000) // timeout for registerValidator requests - relays relayList - relayMonitors relayMonitorList + relays relayList + relayMonitors relayMonitorList + privilegedBuilders privilegedBuilderList // cli flags printVersion = flag.Bool("version", false, "only print version") @@ -66,11 +68,12 @@ var ( logService = flag.String("log-service", defaultLogServiceTag, "add a 'service=...' tag to all log messages") logNoVersion = flag.Bool("log-no-version", defaultDisableLogVersion, "disables adding the version to every log entry") - listenAddr = flag.String("addr", defaultListenAddr, "listen-address for mev-boost server") - relayURLs = flag.String("relays", defaultRelays, "relay urls - single entry or comma-separated list (scheme://pubkey@host)") - relayCheck = flag.Bool("relay-check", defaultRelayCheck, "check relay status on startup and on the status API call") - relayMinBidEth = flag.Float64("min-bid", defaultRelayMinBidEth, "minimum bid to accept from a relay [eth]") - relayMonitorURLs = flag.String("relay-monitors", defaultRelayMonitors, "relay monitor urls - single entry or comma-separated list (scheme://host)") + listenAddr = flag.String("addr", defaultListenAddr, "listen-address for mev-boost server") + relayURLs = flag.String("relays", defaultRelays, "relay urls - single entry or comma-separated list (scheme://pubkey@host)") + relayCheck = flag.Bool("relay-check", defaultRelayCheck, "check relay status on startup and on the status API call") + relayMinBidEth = flag.Float64("min-bid", defaultRelayMinBidEth, "minimum bid to accept from a relay [eth]") + relayMonitorURLs = flag.String("relay-monitors", defaultRelayMonitors, "relay monitor urls - single entry or comma-separated list (scheme://host)") + privilegedBuilderKeys = flag.String("privileged-builders", defaultPrivilegedBuilders, "single entry or comma-separated list of relay username (pubkey)") relayTimeoutMsGetHeader = flag.Int("request-timeout-getheader", defaultTimeoutMsGetHeader, "timeout for getHeader requests to the relay [ms]") relayTimeoutMsGetPayload = flag.Int("request-timeout-getpayload", defaultTimeoutMsGetPayload, "timeout for getPayload requests to the relay [ms]") @@ -95,6 +98,7 @@ func Main() { // process repeatable flags flag.Var(&relays, "relay", "a single relay, can be specified multiple times") flag.Var(&relayMonitors, "relay-monitor", "a single relay monitor, can be specified multiple times") + flag.Var(&privilegedBuilders, "privileged-builder", "a single privileged builder, can be specified multiple times") // parse flags and get started flag.Parse() @@ -149,13 +153,24 @@ func Main() { } } + // set relay priorities + if *privilegedBuilderKeys != "" { + for _, builderKey := range strings.Split(*privilegedBuilderKeys, ",") { + err := privilegedBuilders.Set(strings.TrimSpace(builderKey)) + if err != nil { + log.WithError(err).WithField("privilegedBuilder", builderKey).Fatal("Invalid privileged builder") + } + } + } + if len(relays) == 0 { flag.Usage() log.Fatal("no relays specified") } log.Infof("using %d relays", len(relays)) for index, relay := range relays { - log.Infof("relay #%d: %s", index+1, relay.String()) + isPrivileged := privilegedBuilders.Contains(relay.PublicKey) + log.Infof("relay #%d: %s, privileged %t", index+1, relay.String(), isPrivileged) } // For backwards compatibility with the -relay-monitors flag. @@ -188,6 +203,7 @@ func Main() { ListenAddr: *listenAddr, Relays: relays, RelayMonitors: relayMonitors, + PrivilegedBuilders: privilegedBuilders, GenesisForkVersionHex: genesisForkVersionHex, GenesisTime: genesisTime, RelayCheck: *relayCheck, diff --git a/cli/types.go b/cli/types.go index 28c99b39..1b6cde8b 100644 --- a/cli/types.go +++ b/cli/types.go @@ -1,10 +1,13 @@ package cli import ( + "bytes" "errors" "net/url" "strings" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/flashbots/go-boost-utils/utils" "github.com/flashbots/mev-boost/server/types" ) @@ -67,3 +70,34 @@ func (rm *relayMonitorList) Set(value string) error { *rm = append(*rm, relayMonitor) return nil } + +type privilegedBuilderList []phase0.BLSPubKey + +func (pb *privilegedBuilderList) String() string { + privilegedBuilders := []string{} + for _, privilegedBuilder := range *pb { + privilegedBuilders = append(privilegedBuilders, privilegedBuilder.String()) + } + return strings.Join(privilegedBuilders, ",") +} + +func (pb *privilegedBuilderList) Contains(privilegedBuilder phase0.BLSPubKey) bool { + for _, entry := range *pb { + if bytes.Equal(entry[:], privilegedBuilder[:]) { + return true + } + } + return false +} + +func (pb *privilegedBuilderList) Set(value string) error { + privilegedBuilder, err := utils.HexToPubkey(value) + if err != nil { + return err + } + if pb.Contains(privilegedBuilder) { + return errDuplicateEntry + } + *pb = append(*pb, privilegedBuilder) + return nil +} diff --git a/server/mock/mock_relay.go b/server/mock/mock_relay.go index c74d2c87..8be753b9 100644 --- a/server/mock/mock_relay.go +++ b/server/mock/mock_relay.go @@ -29,16 +29,6 @@ import ( "github.com/stretchr/testify/require" ) -const ( - mockRelaySecretKeyHex = "0x4e343a647c5a5c44d76c2c58b63f02cdf3a9a0ec40f102ebc26363b4b1b95033" -) - -var ( - skBytes, _ = hexutil.Decode(mockRelaySecretKeyHex) - mockRelaySecretKey, _ = bls.SecretKeyFromBytes(skBytes) - mockRelayPublicKey, _ = bls.PublicKeyFromSecretKey(mockRelaySecretKey) -) - // Relay is used to fake a relay's behavior. // You can override each of its handler by setting the instance's HandlerOverride_METHOD_TO_OVERRIDE to your own // handler. @@ -73,7 +63,11 @@ type Relay struct { // A secret key must be provided to sign default and custom response messages func NewRelay(t *testing.T) *Relay { t.Helper() - relay := &Relay{t: t, secretKey: mockRelaySecretKey, publicKey: mockRelayPublicKey, requestCount: make(map[string]int)} + + relay := &Relay{t: t, requestCount: make(map[string]int)} + + relay.secretKey, _, _ = bls.GenerateNewKeypair() + relay.publicKey, _ = bls.PublicKeyFromSecretKey(relay.secretKey) // Initialize server relay.Server = httptest.NewServer(relay.getRouter()) @@ -81,7 +75,7 @@ func NewRelay(t *testing.T) *Relay { // Create the RelayEntry with correct pubkey url, err := url.Parse(relay.Server.URL) require.NoError(t, err) - urlWithKey := fmt.Sprintf("%s://%s@%s", url.Scheme, hexutil.Encode(bls.PublicKeyToBytes(mockRelayPublicKey)), url.Host) + urlWithKey := fmt.Sprintf("%s://%s@%s", url.Scheme, hexutil.Encode(bls.PublicKeyToBytes(relay.publicKey)), url.Host) relay.RelayEntry, err = types.NewRelayEntry(urlWithKey) require.NoError(t, err) return relay @@ -248,7 +242,7 @@ func (m *Relay) defaultHandleGetHeader(w http.ResponseWriter) { 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + m.RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) if m.GetHeaderResponse != nil { diff --git a/server/service.go b/server/service.go index 73aa21fd..c1423288 100644 --- a/server/service.go +++ b/server/service.go @@ -62,6 +62,7 @@ type BoostServiceOpts struct { ListenAddr string Relays []types.RelayEntry RelayMonitors []*url.URL + PrivilegedBuilders []phase0.BLSPubKey GenesisForkVersionHex string GenesisTime uint64 RelayCheck bool @@ -75,14 +76,15 @@ type BoostServiceOpts struct { // BoostService - the mev-boost service type BoostService struct { - listenAddr string - relays []types.RelayEntry - relayMonitors []*url.URL - log *logrus.Entry - srv *http.Server - relayCheck bool - relayMinBid types.U256Str - genesisTime uint64 + listenAddr string + relays []types.RelayEntry + relayMonitors []*url.URL + privilegedBuilders []phase0.BLSPubKey + log *logrus.Entry + srv *http.Server + relayCheck bool + relayMinBid types.U256Str + genesisTime uint64 builderSigningDomain phase0.Domain httpClientGetHeader http.Client @@ -109,15 +111,16 @@ func NewBoostService(opts BoostServiceOpts) (*BoostService, error) { } return &BoostService{ - listenAddr: opts.ListenAddr, - relays: opts.Relays, - relayMonitors: opts.RelayMonitors, - log: opts.Log, - relayCheck: opts.RelayCheck, - relayMinBid: opts.RelayMinBid, - genesisTime: opts.GenesisTime, - bids: make(map[bidRespKey]bidResp), - slotUID: &slotUID{}, + listenAddr: opts.ListenAddr, + relays: opts.Relays, + relayMonitors: opts.RelayMonitors, + privilegedBuilders: opts.PrivilegedBuilders, + log: opts.Log, + relayCheck: opts.RelayCheck, + relayMinBid: opts.RelayMinBid, + genesisTime: opts.GenesisTime, + bids: make(map[bidRespKey]bidResp), + slotUID: &slotUID{}, builderSigningDomain: builderSigningDomain, httpClientGetHeader: http.Client{ @@ -292,7 +295,7 @@ func (m *BoostService) handleRegisterValidator(w http.ResponseWriter, req *http. } // handleGetHeader requests bids from the relays -func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) { +func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) { //nolint:maintidx vars := mux.Vars(req) slot := vars["slot"] parentHashHex := vars["parent_hash"] @@ -349,6 +352,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) } // Prepare relay responses result := bidResp{} // the final response, containing the highest bid (if any) + resultPrivileged := bidResp{} // the final response, containing the highest bid (if any) for privileged relays relays := make(map[BlockHashHex][]types.RelayEntry) // relays that sent the bid for a specific blockHash // Call the relays var mu sync.Mutex @@ -444,35 +448,46 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) // Remember which relays delivered which bids (multiple relays might deliver the top bid) relays[BlockHashHex(bidInfo.blockHash.String())] = append(relays[BlockHashHex(bidInfo.blockHash.String())], relay) - // Compare the bid with already known top bid (if any) - if !result.response.IsEmpty() { - valueDiff := bidInfo.value.Cmp(result.bidInfo.value) - if valueDiff == -1 { // current bid is less profitable than already known one - return - } else if valueDiff == 0 { // current bid is equally profitable as already known one. Use hash as tiebreaker - previousBidBlockHash := result.bidInfo.blockHash - if bidInfo.blockHash.String() >= previousBidBlockHash.String() { - return - } - } + if m.isPrivilegedRelay(relay.PublicKey) { + m.setBestBid(&resultPrivileged, bidInfo, responsePayload, log) + } else { + m.setBestBid(&result, bidInfo, responsePayload, log) } - - // Use this relay's response as mev-boost response because it's most profitable - log.Debug("new best bid") - result.response = *responsePayload - result.bidInfo = bidInfo - result.t = time.Now() }(relay) } // Wait for all requests to complete... wg.Wait() - if result.response.IsEmpty() { + if resultPrivileged.response.IsEmpty() && result.response.IsEmpty() { log.Info("no bid received") w.WriteHeader(http.StatusNoContent) return } + if !resultPrivileged.response.IsEmpty() { + // Log result privileged + valueEth := weiBigIntToEthBigFloat(resultPrivileged.bidInfo.value.ToBig()) + resultPrivileged.relays = relays[BlockHashHex(resultPrivileged.bidInfo.blockHash.String())] + log.WithFields(logrus.Fields{ + "blockHash": resultPrivileged.bidInfo.blockHash.String(), + "blockNumber": resultPrivileged.bidInfo.blockNumber, + "txRoot": resultPrivileged.bidInfo.txRoot.String(), + "value": valueEth.Text('f', 18), + "relays": strings.Join(types.RelayEntriesToStrings(resultPrivileged.relays), ", "), + "privileged": true, + }).Info("best privileged bid") + + // Remember the bid, for future logging in case of withholding + bidKey := bidRespKey{slot: _slot, blockHash: resultPrivileged.bidInfo.blockHash.String()} + m.bidsLock.Lock() + m.bids[bidKey] = resultPrivileged + m.bidsLock.Unlock() + + // Return the bid + m.respondOK(w, &resultPrivileged.response) + return + } + // Log result valueEth := weiBigIntToEthBigFloat(result.bidInfo.value.ToBig()) result.relays = relays[BlockHashHex(result.bidInfo.blockHash.String())] @@ -482,6 +497,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) "txRoot": result.bidInfo.txRoot.String(), "value": valueEth.Text('f', 18), "relays": strings.Join(types.RelayEntriesToStrings(result.relays), ", "), + "privileged": false, }).Info("best bid") // Remember the bid, for future logging in case of withholding @@ -494,6 +510,27 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) m.respondOK(w, &result.response) } +func (m *BoostService) setBestBid(result *bidResp, bidInfo bidInfo, responsePayload *builderSpec.VersionedSignedBuilderBid, log *logrus.Entry) { + // Compare the bid with already known top bid (if any) + if !result.response.IsEmpty() { + valueDiff := bidInfo.value.Cmp(result.bidInfo.value) + if valueDiff == -1 { // current bid is less profitable than already known one + return + } else if valueDiff == 0 { // current bid is equally profitable as already known one. Use hash as tiebreaker + previousBidBlockHash := result.bidInfo.blockHash + if bidInfo.blockHash.String() >= previousBidBlockHash.String() { + return + } + } + } + + // Use this relay's response as mev-boost response because it's most profitable + log.Debug("new best bid") + result.response = *responsePayload + result.bidInfo = bidInfo + result.t = time.Now() +} + func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Request, log *logrus.Entry, payload *eth2ApiV1Capella.SignedBlindedBeaconBlock, body []byte) { if payload.Message == nil || payload.Message.Body == nil || payload.Message.Body.ExecutionPayloadHeader == nil { log.WithField("body", string(body)).Error("missing parts of the request payload from the beacon-node") @@ -834,3 +871,12 @@ func (m *BoostService) CheckRelays() int { wg.Wait() return int(numSuccessRequestsToRelay) } + +func (m *BoostService) isPrivilegedRelay(pubkey phase0.BLSPubKey) bool { + for _, builder := range m.privilegedBuilders { + if bytes.Equal(builder[:], pubkey[:]) { + return true + } + } + return false +} diff --git a/server/service_test.go b/server/service_test.go index 47d418aa..f8502d2e 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -72,6 +72,17 @@ func newTestBackend(t *testing.T, numRelays int, relayTimeout time.Duration) *te return &backend } +func (be *testBackend) setPrivilegedBuilders(pubKey phase0.BLSPubKey) *testBackend { + privilegedBuilders := make([]phase0.BLSPubKey, 0) + for _, relay := range be.relays { + if bytes.Equal(relay.RelayEntry.PublicKey[:], pubKey[:]) { + privilegedBuilders = append(privilegedBuilders, relay.RelayEntry.PublicKey) + } + } + be.boost.privilegedBuilders = privilegedBuilders + return be +} + func (be *testBackend) request(t *testing.T, method, path string, payload any) *httptest.ResponseRecorder { t.Helper() var req *http.Request @@ -333,7 +344,7 @@ func TestGetHeader(t *testing.T) { 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionDeneb, ) backend.relays[0].GetHeaderResponse = resp @@ -348,7 +359,7 @@ func TestGetHeader(t *testing.T) { 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) resp.Capella.Message.Header.BlockHash = nilHash @@ -375,7 +386,7 @@ func TestGetHeader(t *testing.T) { 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -397,7 +408,7 @@ func TestGetHeader(t *testing.T) { 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -469,7 +480,7 @@ func TestGetHeaderBids(t *testing.T) { 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -478,7 +489,7 @@ func TestGetHeaderBids(t *testing.T) { 12347, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[1].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -487,7 +498,7 @@ func TestGetHeaderBids(t *testing.T) { 12346, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[2].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -518,7 +529,7 @@ func TestGetHeaderBids(t *testing.T) { 12345, "0xa38385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -526,7 +537,7 @@ func TestGetHeaderBids(t *testing.T) { 12345, "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[1].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -534,7 +545,7 @@ func TestGetHeaderBids(t *testing.T) { 12345, "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[2].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -570,7 +581,7 @@ func TestGetHeaderBids(t *testing.T) { 12344, "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -593,7 +604,7 @@ func TestGetHeaderBids(t *testing.T) { 12345, "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) @@ -611,6 +622,42 @@ func TestGetHeaderBids(t *testing.T) { require.NoError(t, err) require.Equal(t, uint256.NewInt(12345), value) }) + + t.Run("Use header from a privileged relay", func(t *testing.T) { + backend := newTestBackend(t, 2, time.Second) + backend.setPrivilegedBuilders(backend.relays[0].RelayEntry.PublicKey) + + // privileged relay + backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( + 12348, + "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + backend.relays[0].RelayEntry.PublicKey.String(), + spec.DataVersionCapella, + ) + // non-privileged relay with higher value + backend.relays[1].GetHeaderResponse = backend.relays[1].MakeGetHeaderResponse( + 12349, + "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + backend.relays[1].RelayEntry.PublicKey.String(), + spec.DataVersionCapella, + ) + + rr := backend.request(t, http.MethodGet, path, nil) + + require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) + require.Equal(t, 1, backend.relays[1].GetRequestCount(path)) + + require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) + // Highest value should be 12348, i.e. privileged relay. + resp := new(builderSpec.VersionedSignedBuilderBid) + err := json.Unmarshal(rr.Body.Bytes(), resp) + require.NoError(t, err) + value, err := resp.Value() + require.NoError(t, err) + require.Equal(t, uint256.NewInt(12348), value) + }) } func TestGetPayload(t *testing.T) { @@ -883,7 +930,7 @@ func TestGetPayloadToAllRelays(t *testing.T) { 12345, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", - "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + backend.relays[0].RelayEntry.PublicKey.String(), spec.DataVersionCapella, ) rr := backend.request(t, http.MethodGet, getHeaderPath, nil)