From bccaf782b24b4d39c49e4a368fe579c391649b0f Mon Sep 17 00:00:00 2001 From: Chris Hager Date: Thu, 22 Sep 2022 11:58:01 +0200 Subject: [PATCH] getPayload only to origin relays (#342) --- CONTRIBUTING.md | 17 +++++++++++- cli/main.go | 10 +------ server/mock_relay.go | 7 ++--- server/relay_entry.go | 9 +++++++ server/service.go | 31 +++++++++++----------- server/service_test.go | 60 +++++++++++++++++++++++++++++++++++++----- server/utils.go | 5 +++- 7 files changed, 104 insertions(+), 35 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f5728f9..b7eff96f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,7 +93,22 @@ We appreciate you, friend <3. The extended deploy steps are necessary for installations with `go install` to include the correct version. -* Ensure linter and tests are working: `make lint && make test-race` +### Before the release + +Run these commands: + +```bash +make lint +make test-race + +# Start mev-boost with relay check, and call the mev-boost status endpoint +go run . -mainnet -relay-check -relays https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net,https://0x8b5d2e73e2a3a55c6c87b8b6eb92e0149a125c852751db1422fa951e42a09b82c142c3ea98d0d9930b056a3bc9896b8f@bloxroute.max-profit.blxrbdn.com,https://0xb3ee7afcf27f1f1259ac1787876318c6584ee353097a50ed84f51a1f21a323b3736f271a895c7ce918c038e4265918be@relay.edennetwork.io,https://0x9000009807ed12c1f08bf4e81c6da3ba8e3fc3d953898ce0102433094e5f22f21102ec057841fcb81978ed1ea0fa8246@builder-relay-mainnet.blocknative.com -debug +curl localhost:18550/eth/v1/builder/status +``` + + +### Releasing a new version + * Update `Version` in `config/vars.go` * it will be next_version-dev (eg. `v0.7.11-dev`) * change it to the next version (eg. `v0.7.11`), and commit diff --git a/cli/main.go b/cli/main.go index 4a4de16e..c11e0477 100644 --- a/cli/main.go +++ b/cli/main.go @@ -119,7 +119,7 @@ func Main() { flag.Usage() log.Fatal("no relays specified") } - log.WithField("relays", relaysToStrings(relays)).Infof("using %d relays", len(relays)) + log.WithField("relays", server.RelayEntriesToStrings(relays)).Infof("using %d relays", len(relays)) relayMonitors := parseRelayMonitorURLs(*relayMonitorURLs) if len(relayMonitors) > 0 { @@ -193,11 +193,3 @@ func parseRelayMonitorURLs(relayMonitorURLs string) (ret []*url.URL) { } return ret } - -func relaysToStrings(relays []server.RelayEntry) []string { - ret := []string{} - for _, entry := range relays { - ret = append(ret, entry.String()) - } - return ret -} diff --git a/server/mock_relay.go b/server/mock_relay.go index 45a33570..18d2f00a 100644 --- a/server/mock_relay.go +++ b/server/mock_relay.go @@ -149,12 +149,12 @@ func (m *mockRelay) handleRegisterValidator(w http.ResponseWriter, req *http.Req // MakeGetHeaderResponse is used to create the default or can be used to create a custom response to the getHeader // method -func (m *mockRelay) MakeGetHeaderResponse(value uint64, hash, publicKey string) *types.GetHeaderResponse { +func (m *mockRelay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, publicKey string) *types.GetHeaderResponse { // Fill the payload with custom values. message := &types.BuilderBid{ Header: &types.ExecutionPayloadHeader{ - BlockHash: _HexToHash(hash), - ParentHash: _HexToHash("0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7"), + BlockHash: _HexToHash(blockHash), + ParentHash: _HexToHash(parentHash), }, Value: types.IntToU256(value), Pubkey: _HexToPubkey(publicKey), @@ -191,6 +191,7 @@ func (m *mockRelay) handleGetHeader(w http.ResponseWriter, req *http.Request) { response := m.MakeGetHeaderResponse( 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) if m.GetHeaderResponse != nil { diff --git a/server/relay_entry.go b/server/relay_entry.go index cba572f6..05fe9b9c 100644 --- a/server/relay_entry.go +++ b/server/relay_entry.go @@ -44,3 +44,12 @@ func NewRelayEntry(relayURL string) (entry RelayEntry, err error) { err = entry.PublicKey.UnmarshalText([]byte(entry.URL.User.Username())) return entry, err } + +// RelayEntriesToStrings returns the string representation of a list of relay entries +func RelayEntriesToStrings(relays []RelayEntry) []string { + ret := make([]string, len(relays)) + for i, entry := range relays { + ret[i] = entry.String() + } + return ret +} diff --git a/server/service.go b/server/service.go index 423e9618..a1a08f05 100644 --- a/server/service.go +++ b/server/service.go @@ -286,13 +286,11 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) return } - var mu sync.Mutex - relays := make(map[string][]string) // relays per blockHash - result := bidResp{} - - ua := UserAgent(req.Header.Get("User-Agent")) + result := bidResp{} // the final response, containing the highest bid (if any) + relays := make(map[BlockHashHex][]RelayEntry) // relays that sent the bid for a specific blockHash // Call the relays + var mu sync.Mutex var wg sync.WaitGroup for _, relay := range m.relays { wg.Add(1) @@ -302,7 +300,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) url := relay.GetURI(path) log := log.WithField("url", url) responsePayload := new(types.GetHeaderResponse) - code, err := SendHTTPRequest(context.Background(), m.httpClientGetHeader, http.MethodGet, url, ua, nil, responsePayload) + code, err := SendHTTPRequest(context.Background(), m.httpClientGetHeader, http.MethodGet, url, UserAgent(req.Header.Get("User-Agent")), nil, responsePayload) if err != nil { log.WithError(err).Warn("error making request to relay") return @@ -365,11 +363,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) defer mu.Unlock() // Remember which relays delivered which bids (multiple relays might deliver the top bid) - if _, ok := relays[blockHash]; !ok { - relays[blockHash] = []string{relay.String()} - } else { - relays[blockHash] = append(relays[blockHash], relay.String()) - } + relays[BlockHashHex(blockHash)] = append(relays[BlockHashHex(blockHash)], relay) // Compare the bid with already known top bid (if any) if result.response.Data != nil { @@ -402,13 +396,13 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) } // Log result - result.relays = relays[result.blockHash] + result.relays = relays[BlockHashHex(result.blockHash)] log.WithFields(logrus.Fields{ "blockHash": result.blockHash, "blockNumber": result.response.Data.Message.Header.BlockNumber, "txRoot": result.response.Data.Message.Header.TransactionsRoot.String(), "value": result.response.Data.Message.Value.String(), - "relays": strings.Join(result.relays, ", "), + "relays": strings.Join(RelayEntriesToStrings(result.relays), ", "), }).Info("best bid") // Remember the bid, for future logging in case of withholding @@ -467,6 +461,12 @@ func (m *BoostService) handleGetPayload(w http.ResponseWriter, req *http.Request log.Warn("bid found but no associated relays") } + relays := originalBid.relays + if len(relays) == 0 { + log.Warn("originating relay not found, sending getPayload request to all relays") + relays = m.relays + } + var wg sync.WaitGroup var mu sync.Mutex result := new(types.GetPayloadResponse) @@ -476,7 +476,7 @@ func (m *BoostService) handleGetPayload(w http.ResponseWriter, req *http.Request requestCtx, requestCtxCancel := context.WithCancel(context.Background()) defer requestCtxCancel() - for _, relay := range m.relays { + for _, relay := range relays { wg.Add(1) go func(relay RelayEntry) { defer wg.Done() @@ -525,7 +525,8 @@ func (m *BoostService) handleGetPayload(w http.ResponseWriter, req *http.Request // If no payload has been received from relay, log loudly about withholding! if result.Data == nil || result.Data.BlockHash == nilHash { - log.WithField("relays", strings.Join(originalBid.relays, ", ")).Error("no payload received from relay -- could be a network error or withholding.") + originRelays := RelayEntriesToStrings(originalBid.relays) + log.WithField("relays", strings.Join(originRelays, ", ")).Error("no payload received from relay!") m.respondError(w, http.StatusBadGateway, errNoSuccessfulRelayResponse.Error()) return } diff --git a/server/service_test.go b/server/service_test.go index 01b8e23d..7c4778c5 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -234,15 +234,15 @@ func TestRegisterValidator(t *testing.T) { }) } -func TestGetHeader(t *testing.T) { - getPath := func(slot uint64, parentHash types.Hash, pubkey types.PublicKey) string { - return fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", slot, parentHash.String(), pubkey.String()) - } +func getHeaderPath(slot uint64, parentHash types.Hash, pubkey types.PublicKey) string { + return fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", slot, parentHash.String(), pubkey.String()) +} +func TestGetHeader(t *testing.T) { hash := _HexToHash("0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7") pubkey := _HexToPubkey( "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249") - path := getPath(1, hash, pubkey) + path := getHeaderPath(1, hash, pubkey) require.Equal(t, "/eth/v1/builder/header/1/0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7/0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", path) t.Run("Okay response from relay", func(t *testing.T) { @@ -257,6 +257,7 @@ func TestGetHeader(t *testing.T) { resp := backend.relays[0].MakeGetHeaderResponse( 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) resp.Data.Message.Header.BlockHash = nilHash @@ -284,6 +285,7 @@ func TestGetHeader(t *testing.T) { backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) @@ -291,6 +293,7 @@ func TestGetHeader(t *testing.T) { backend.relays[1].GetHeaderResponse = backend.relays[1].MakeGetHeaderResponse( 12347, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) @@ -298,6 +301,7 @@ func TestGetHeader(t *testing.T) { backend.relays[2].GetHeaderResponse = backend.relays[2].MakeGetHeaderResponse( 12346, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) @@ -325,18 +329,21 @@ func TestGetHeader(t *testing.T) { backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( 12345, "0xa38385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) backend.relays[1].GetHeaderResponse = backend.relays[1].MakeGetHeaderResponse( 12345, "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) backend.relays[2].GetHeaderResponse = backend.relays[2].MakeGetHeaderResponse( 12345, "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) @@ -364,6 +371,7 @@ func TestGetHeader(t *testing.T) { backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) @@ -384,6 +392,7 @@ func TestGetHeader(t *testing.T) { backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( 12345, "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", ) @@ -432,7 +441,7 @@ func TestGetHeader(t *testing.T) { t.Run("Invalid parent hash", func(t *testing.T) { backend := newTestBackend(t, 1, time.Second) - invalidParentHashPath := getPath(1, types.Hash{}, pubkey) + invalidParentHashPath := getHeaderPath(1, types.Hash{}, pubkey) rr := backend.request(t, http.MethodGet, invalidParentHashPath, nil) require.Equal(t, http.StatusNoContent, rr.Code) require.Equal(t, 0, backend.relays[0].GetRequestCount(path)) @@ -579,3 +588,42 @@ func TestGetPayloadWithTestdata(t *testing.T) { }) } } + +func TestGetPayloadToOriginRelayOnly(t *testing.T) { + // Load the signed blinded beacon block used for getPayload + jsonFile, err := os.Open("../testdata/kiln-signed-blinded-beacon-block-899730.json") + require.NoError(t, err) + defer jsonFile.Close() + signedBlindedBeaconBlock := new(types.SignedBlindedBeaconBlock) + require.NoError(t, DecodeJSON(jsonFile, &signedBlindedBeaconBlock)) + + // Create a test backend with 2 relays + backend := newTestBackend(t, 2, time.Second) + + // call getHeader, highest bid is returned by relay 0 + getHeaderPath := "/eth/v1/builder/header/899730/0xe8b9bd82aa0e957736c5a029903e53d581edf451e28ab274f4ba314c442e35a4/0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249" + backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( + 12345, + "0x373fb4e59dcb659b94bd58595c25345333426aa639f821567103e2eccf34d126", + "0xe8b9bd82aa0e957736c5a029903e53d581edf451e28ab274f4ba314c442e35a4", + "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + ) + rr := backend.request(t, http.MethodGet, getHeaderPath, nil) + require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) + require.Equal(t, 1, backend.relays[0].GetRequestCount(getHeaderPath)) + require.Equal(t, 1, backend.relays[1].GetRequestCount(getHeaderPath)) + + // Prepare getPayload response + backend.relays[0].GetPayloadResponse = &types.GetPayloadResponse{ + Data: &types.ExecutionPayload{ + BlockHash: signedBlindedBeaconBlock.Message.Body.ExecutionPayloadHeader.BlockHash, + }, + } + + // call getPayload, ensure it's only called on relay 0 (origin of the bid) + getPayloadPath := "/eth/v1/builder/blinded_blocks" + rr = backend.request(t, http.MethodPost, getPayloadPath, signedBlindedBeaconBlock) + require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) + require.Equal(t, 1, backend.relays[0].GetRequestCount(getPayloadPath)) + require.Equal(t, 0, backend.relays[1].GetRequestCount(getPayloadPath)) +} diff --git a/server/utils.go b/server/utils.go index 10e56632..1e204840 100644 --- a/server/utils.go +++ b/server/utils.go @@ -21,6 +21,9 @@ import ( // UserAgent is a custom string type to avoid confusing url + userAgent parameters in SendHTTPRequest type UserAgent string +// BlockHashHex is a hex-string representation of a block hash +type BlockHashHex string + // SendHTTPRequest - prepare and send HTTP request, marshaling the payload if any, and decoding the response if dst is set func SendHTTPRequest(ctx context.Context, client http.Client, method, url string, userAgent UserAgent, payload any, dst any) (code int, err error) { var req *http.Request @@ -114,7 +117,7 @@ type bidResp struct { t time.Time response types.GetHeaderResponse blockHash string - relays []string + relays []RelayEntry } // bidRespKey is used as key for the bids cache