diff --git a/go.mod b/go.mod index a2faa739..ebf9e949 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( require ( github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/VictoriaMetrics/fastcache v1.6.0 // indirect - github.com/attestantio/go-builder-client v0.3.1 + github.com/attestantio/go-builder-client v0.3.2-0.20230626105718-423f7ec4ad24 github.com/attestantio/go-eth2-client v0.17.0 github.com/btcsuite/btcd v0.22.0-beta // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect diff --git a/go.sum b/go.sum index aafe4ed8..210b2fcf 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/attestantio/go-builder-client v0.3.1 h1:yKULPmv9IymY2eJ0VzdRfqjUUz/8yRO66Ym6Wn8RJM0= -github.com/attestantio/go-builder-client v0.3.1/go.mod h1:DwesMTOqnCp4u+n3uZ+fWL8wwnSBZVD9VMIVPDR+AZE= +github.com/attestantio/go-builder-client v0.3.2-0.20230626105718-423f7ec4ad24 h1:bBI7XFmYLhzVOcWG9VkU6DmjoJd51jrT84ZZuY/DZ7E= +github.com/attestantio/go-builder-client v0.3.2-0.20230626105718-423f7ec4ad24/go.mod h1:DwesMTOqnCp4u+n3uZ+fWL8wwnSBZVD9VMIVPDR+AZE= github.com/attestantio/go-eth2-client v0.17.0 h1:Rvn/tmLHRRztoS2c/6AmsslGucyytWMvnQlAUz4EvYY= github.com/attestantio/go-eth2-client v0.17.0/go.mod h1:N+BNIxaHmul44d0tTjwFrUJU/g6MIblFP67oagm1Lwo= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/server/mock_relay.go b/server/mock_relay.go index 34219468..4e86170a 100644 --- a/server/mock_relay.go +++ b/server/mock_relay.go @@ -11,11 +11,12 @@ import ( "time" "github.com/attestantio/go-builder-client/api" - "github.com/attestantio/go-builder-client/api/capella" + apibellatrix "github.com/attestantio/go-builder-client/api/bellatrix" + apicapella "github.com/attestantio/go-builder-client/api/capella" "github.com/attestantio/go-builder-client/spec" consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" - capellaspec "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/flashbots/go-boost-utils/bls" @@ -57,7 +58,7 @@ type mockRelay struct { handlerOverrideGetPayload func(w http.ResponseWriter, req *http.Request) // Default responses placeholders, used if overrider does not exist - GetHeaderResponse *GetHeaderResponse + GetHeaderResponse *spec.VersionedSignedBuilderBid GetBellatrixPayloadResponse *types.GetPayloadResponse GetCapellaPayloadResponse *api.VersionedExecutionPayload @@ -165,36 +166,34 @@ func (m *mockRelay) defaultHandleRegisterValidator(w http.ResponseWriter, req *h // 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, blockHash, parentHash, publicKey string, version consensusspec.DataVersion) *GetHeaderResponse { +func (m *mockRelay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, publicKey string, version consensusspec.DataVersion) *spec.VersionedSignedBuilderBid { switch version { case consensusspec.DataVersionBellatrix: // Fill the payload with custom values. - message := &types.BuilderBid{ - Header: &types.ExecutionPayloadHeader{ - BlockHash: _HexToHash(blockHash), - ParentHash: _HexToHash(parentHash), + message := &apibellatrix.BuilderBid{ + Header: &bellatrix.ExecutionPayloadHeader{ + BlockHash: phase0.Hash32(_HexToHash(blockHash)), + ParentHash: phase0.Hash32(_HexToHash(parentHash)), }, - Value: types.IntToU256(value), - Pubkey: _HexToPubkey(publicKey), + Value: uint256.NewInt(value), + Pubkey: phase0.BLSPubKey(_HexToPubkey(publicKey)), } // Sign the message. signature, err := types.SignMessage(message, types.DomainBuilder, m.secretKey) require.NoError(m.t, err) - return &GetHeaderResponse{ - Bellatrix: &types.GetHeaderResponse{ - Version: "bellatrix", - Data: &types.SignedBuilderBid{ - Message: message, - Signature: signature, - }, + return &spec.VersionedSignedBuilderBid{ + Version: consensusspec.DataVersionCapella, + Bellatrix: &apibellatrix.SignedBuilderBid{ + Message: message, + Signature: phase0.BLSSignature(signature), }, } case consensusspec.DataVersionCapella: // Fill the payload with custom values. - message := &capella.BuilderBid{ - Header: &capellaspec.ExecutionPayloadHeader{ + message := &apicapella.BuilderBid{ + Header: &capella.ExecutionPayloadHeader{ BlockHash: phase0.Hash32(_HexToHash(blockHash)), ParentHash: phase0.Hash32(_HexToHash(parentHash)), WithdrawalsRoot: phase0.Root{}, @@ -207,13 +206,11 @@ func (m *mockRelay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, p signature, err := types.SignMessage(message, types.DomainBuilder, m.secretKey) require.NoError(m.t, err) - return &GetHeaderResponse{ - Capella: &spec.VersionedSignedBuilderBid{ - Version: consensusspec.DataVersionCapella, - Capella: &capella.SignedBuilderBid{ - Message: message, - Signature: phase0.BLSSignature(signature), - }, + return &spec.VersionedSignedBuilderBid{ + Version: consensusspec.DataVersionCapella, + Capella: &apicapella.SignedBuilderBid{ + Message: message, + Signature: phase0.BLSSignature(signature), }, } case consensusspec.DataVersionAltair, consensusspec.DataVersionPhase0, consensusspec.DataVersionDeneb: @@ -263,12 +260,12 @@ func (m *mockRelay) defaultHandleGetHeader(w http.ResponseWriter) { func (m *mockRelay) MakeGetPayloadResponse(parentHash, blockHash, feeRecipient string, blockNumber uint64) *api.VersionedExecutionPayload { return &api.VersionedExecutionPayload{ Version: consensusspec.DataVersionCapella, - Capella: &capellaspec.ExecutionPayload{ + Capella: &capella.ExecutionPayload{ ParentHash: phase0.Hash32(_HexToHash(parentHash)), BlockHash: phase0.Hash32(_HexToHash(blockHash)), BlockNumber: blockNumber, FeeRecipient: bellatrix.ExecutionAddress(_HexToAddress(feeRecipient)), - Withdrawals: make([]*capellaspec.Withdrawal, 0), + Withdrawals: make([]*capella.Withdrawal, 0), }, } } diff --git a/server/service.go b/server/service.go index 48bfe871..f25250fb 100644 --- a/server/service.go +++ b/server/service.go @@ -16,7 +16,9 @@ import ( "time" "github.com/attestantio/go-builder-client/api" + "github.com/attestantio/go-builder-client/spec" "github.com/attestantio/go-eth2-client/api/v1/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/flashbots/go-boost-utils/types" "github.com/flashbots/go-utils/httplogger" "github.com/flashbots/mev-boost/config" @@ -35,7 +37,7 @@ var ( ) var ( - nilHash = types.Hash{} + nilHash = phase0.Hash32{} nilResponse = struct{}{} ) @@ -46,7 +48,7 @@ type httpErrorResp struct { // AuctionTranscript is the bid and blinded block received from the relay send to the relay monitor type AuctionTranscript struct { - Bid *SignedBuilderBid `json:"bid"` + Bid *spec.VersionedSignedBuilderBid // TODO: proper json marshalling and unmashalling Acceptance *types.SignedBlindedBeaconBlock `json:"acceptance"` } @@ -369,7 +371,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) path := fmt.Sprintf("/eth/v1/builder/header/%s/%s/%s", slot, parentHashHex, pubkey) url := relay.GetURI(path) log := log.WithField("url", url) - responsePayload := new(GetHeaderResponse) + responsePayload := new(spec.VersionedSignedBuilderBid) code, err := SendHTTPRequest(context.Background(), m.httpClientGetHeader, http.MethodGet, url, ua, headers, nil, responsePayload) if err != nil { log.WithError(err).Warn("error making request to relay") @@ -381,28 +383,39 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) return } - // Skip if invalid payload - if responsePayload.IsInvalid() { + // Skip if payload is empty + if responsePayload.IsEmpty() { return } - blockHash := responsePayload.BlockHash() - valueEth := weiBigIntToEthBigFloat(responsePayload.Value()) + // Getting the bid info will check if there are missing fields in the response + bidInfo, err := parseBidInfo(responsePayload) + if err != nil { + log.WithError(err).Warn("error parsing bid info") + return + } + + if bidInfo.blockHash == nilHash { + log.Warn("relay responded with empty block hash") + return + } + + valueEth := weiBigIntToEthBigFloat(bidInfo.value.ToBig()) log = log.WithFields(logrus.Fields{ - "blockNumber": responsePayload.BlockNumber(), - "blockHash": blockHash, - "txRoot": responsePayload.TransactionsRoot(), + "blockNumber": bidInfo.blockNumber, + "blockHash": bidInfo.blockHash.String(), + "txRoot": bidInfo.txRoot.String(), "value": valueEth.Text('f', 18), }) - if relay.PublicKey.String() != responsePayload.Pubkey() { - log.Errorf("bid pubkey mismatch. expected: %s - got: %s", relay.PublicKey.String(), responsePayload.Pubkey()) + if relay.PublicKey.String() != bidInfo.pubkey.String() { + log.Errorf("bid pubkey mismatch. expected: %s - got: %s", relay.PublicKey.String(), bidInfo.pubkey.String()) return } // Verify the relay signature in the relay response if !config.SkipRelaySignatureCheck { - ok, err := types.VerifySignature(responsePayload.Message(), m.builderSigningDomain, relay.PublicKey[:], responsePayload.Signature()) + ok, err := checkRelaySignature(responsePayload, m.builderSigningDomain, relay.PublicKey) if err != nil { log.WithError(err).Error("error verifying relay signature") return @@ -414,17 +427,16 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) } // Verify response coherence with proposer's input data - responseParentHash := responsePayload.ParentHash() - if responseParentHash != parentHashHex { + if bidInfo.parentHash.String() != parentHashHex { log.WithFields(logrus.Fields{ "originalParentHash": parentHashHex, - "responseParentHash": responseParentHash, + "responseParentHash": bidInfo.parentHash.String(), }).Error("proposer and relay parent hashes are not the same") return } - isZeroValue := responsePayload.Value().String() == "0" - isEmptyListTxRoot := responsePayload.TransactionsRoot() == "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1" + isZeroValue := bidInfo.value.String() == "0" + isEmptyListTxRoot := bidInfo.txRoot.String() == "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1" if isZeroValue || isEmptyListTxRoot { log.Warn("ignoring bid with 0 value") return @@ -432,7 +444,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) log.Debug("bid received") // Skip if value (fee) is lower than the minimum bid - if responsePayload.Value().Cmp(m.relayMinBid.BigInt()) == -1 { + if bidInfo.value.ToBig().Cmp(m.relayMinBid.BigInt()) == -1 { log.Debug("ignoring bid below min-bid value") return } @@ -441,16 +453,16 @@ 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) - relays[BlockHashHex(blockHash)] = append(relays[BlockHashHex(blockHash)], relay) + 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 := responsePayload.Value().Cmp(result.response.Value()) + 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.response.BlockHash() - if blockHash >= previousBidBlockHash { + previousBidBlockHash := result.bidInfo.blockHash + if bidInfo.blockHash.String() >= previousBidBlockHash.String() { return } } @@ -459,7 +471,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) // Use this relay's response as mev-boost response because it's most profitable log.Debug("new best bid") result.response = *responsePayload - result.blockHash = blockHash + result.bidInfo = bidInfo result.t = time.Now() }(relay) } @@ -467,25 +479,25 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) // Wait for all requests to complete... wg.Wait() - if result.blockHash == "" { + if result.response.IsEmpty() { log.Info("no bid received") w.WriteHeader(http.StatusNoContent) return } // Log result - valueEth := weiBigIntToEthBigFloat(result.response.Value()) - result.relays = relays[BlockHashHex(result.blockHash)] + valueEth := weiBigIntToEthBigFloat(result.bidInfo.value.ToBig()) + result.relays = relays[BlockHashHex(result.bidInfo.blockHash.String())] log.WithFields(logrus.Fields{ - "blockHash": result.blockHash, - "blockNumber": result.response.BlockNumber(), - "txRoot": result.response.TransactionsRoot(), + "blockHash": result.bidInfo.blockHash.String(), + "blockNumber": result.bidInfo.blockNumber, + "txRoot": result.bidInfo.txRoot, "value": valueEth.Text('f', 18), "relays": strings.Join(RelayEntriesToStrings(result.relays), ", "), }).Info("best bid") // Remember the bid, for future logging in case of withholding - bidKey := bidRespKey{slot: _slot, blockHash: result.blockHash} + bidKey := bidRespKey{slot: _slot, blockHash: result.bidInfo.blockHash.String()} m.bidsLock.Lock() m.bids[bidKey] = result m.bidsLock.Unlock() @@ -535,7 +547,7 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re m.bidsLock.Lock() originalBid := m.bids[bidKey] m.bidsLock.Unlock() - if originalBid.blockHash == "" { + if originalBid.response.IsEmpty() { log.Error("no bid for this getPayload payload found. was getHeader called before?") } else if len(originalBid.relays) == 0 { log.Warn("bid found but no associated relays") @@ -575,7 +587,7 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re return } - if responsePayload.Capella == nil || types.Hash(responsePayload.Capella.BlockHash) == nilHash { + if responsePayload.Capella == nil || responsePayload.Capella.BlockHash == nilHash { log.Error("response with empty data!") return } @@ -618,7 +630,7 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re wg.Wait() // If no payload has been received from relay, log loudly about withholding! - if result.Capella == nil || types.Hash(result.Capella.BlockHash) == nilHash { + if result.Capella == nil || result.Capella.BlockHash == nilHash { originRelays := RelayEntriesToStrings(originalBid.relays) log.WithField("relaysWithBid", strings.Join(originRelays, ", ")).Error("no payload received from relay!") m.respondError(w, http.StatusBadGateway, errNoSuccessfulRelayResponse.Error()) diff --git a/server/service_test.go b/server/service_test.go index 9d497962..8a05e84f 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "math" - "math/big" "net/http" "net/http/httptest" "net/url" @@ -16,6 +15,7 @@ import ( "time" "github.com/attestantio/go-builder-client/api" + "github.com/attestantio/go-builder-client/spec" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" @@ -23,6 +23,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/flashbots/go-boost-utils/types" + "github.com/holiman/uint256" "github.com/prysmaticlabs/go-bitfield" "github.com/stretchr/testify/require" ) @@ -283,7 +284,7 @@ func TestGetHeader(t *testing.T) { "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", consensusspec.DataVersionBellatrix, ) - resp.Bellatrix.Data.Message.Header.BlockHash = nilHash + resp.Bellatrix.Message.Header.BlockHash = nilHash // 1/2 failing responses are okay backend.relays[0].GetHeaderResponse = resp @@ -334,7 +335,7 @@ func TestGetHeader(t *testing.T) { ) // Scramble the signature - backend.relays[0].GetHeaderResponse.Bellatrix.Data.Signature = types.Signature{} + backend.relays[0].GetHeaderResponse.Bellatrix.Signature = phase0.BLSSignature{} rr := backend.request(t, http.MethodGet, path, nil) require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) @@ -402,7 +403,7 @@ func TestGetHeaderBids(t *testing.T) { "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) // First relay will return signed response with value 12347. @@ -411,7 +412,7 @@ func TestGetHeaderBids(t *testing.T) { "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) // First relay will return signed response with value 12346. @@ -420,7 +421,7 @@ func TestGetHeaderBids(t *testing.T) { "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) // Run the request. @@ -434,10 +435,12 @@ func TestGetHeaderBids(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) // Highest value should be 12347, i.e. second relay. - resp := new(GetHeaderResponse) + resp := new(spec.VersionedSignedBuilderBid) err := json.Unmarshal(rr.Body.Bytes(), resp) require.NoError(t, err) - require.Equal(t, big.NewInt(12347), resp.Value()) + value, err := resp.Value() + require.NoError(t, err) + require.Equal(t, uint256.NewInt(12347), value) }) t.Run("Use header with lowest blockhash if same value", func(t *testing.T) { @@ -449,7 +452,7 @@ func TestGetHeaderBids(t *testing.T) { "0xa38385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) backend.relays[1].GetHeaderResponse = backend.relays[1].MakeGetHeaderResponse( @@ -457,7 +460,7 @@ func TestGetHeaderBids(t *testing.T) { "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) backend.relays[2].GetHeaderResponse = backend.relays[2].MakeGetHeaderResponse( @@ -465,7 +468,7 @@ func TestGetHeaderBids(t *testing.T) { "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) // Run the request. @@ -479,12 +482,16 @@ func TestGetHeaderBids(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) // Highest value should be 12347, i.e. second relay. - resp := new(GetHeaderResponse) + resp := new(spec.VersionedSignedBuilderBid) err := json.Unmarshal(rr.Body.Bytes(), resp) require.NoError(t, err) - require.Equal(t, big.NewInt(12345), resp.Value()) - require.Equal(t, "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", resp.BlockHash()) + value, err := resp.Value() + require.NoError(t, err) + require.Equal(t, uint256.NewInt(12345), value) + blockHash, err := resp.BlockHash() + require.NoError(t, err) + require.Equal(t, "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", blockHash.String()) }) t.Run("Respect minimum bid cutoff", func(t *testing.T) { @@ -497,7 +504,7 @@ func TestGetHeaderBids(t *testing.T) { "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) // Run the request. @@ -520,7 +527,7 @@ func TestGetHeaderBids(t *testing.T) { "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", - consensusspec.DataVersionBellatrix, + consensusspec.DataVersionCapella, ) // Run the request. @@ -586,10 +593,12 @@ func TestGetHeaderCapellaBids(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) // Highest value should be 12347, i.e. second relay. - resp := new(GetHeaderResponse) + resp := new(spec.VersionedSignedBuilderBid) err := json.Unmarshal(rr.Body.Bytes(), resp) require.NoError(t, err) - require.Equal(t, big.NewInt(12347), resp.Value()) + value, err := resp.Value() + require.NoError(t, err) + require.Equal(t, uint256.NewInt(12347), value) }) t.Run("Use header with lowest blockhash if same value", func(t *testing.T) { @@ -631,12 +640,16 @@ func TestGetHeaderCapellaBids(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) // Highest value should be 12347, i.e. second relay. - resp := new(GetHeaderResponse) + resp := new(spec.VersionedSignedBuilderBid) err := json.Unmarshal(rr.Body.Bytes(), resp) require.NoError(t, err) - require.Equal(t, big.NewInt(12345), resp.Value()) - require.Equal(t, "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", resp.BlockHash()) + value, err := resp.Value() + require.NoError(t, err) + require.Equal(t, uint256.NewInt(12345), value) + blockHash, err := resp.BlockHash() + require.NoError(t, err) + require.Equal(t, "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", blockHash.String()) }) t.Run("Respect minimum bid cutoff", func(t *testing.T) { @@ -700,7 +713,7 @@ func TestGetHeaderCapellaBids(t *testing.T) { ) // Scramble the signature - backend.relays[0].GetHeaderResponse.Capella.Capella.Signature = phase0.BLSSignature{} + backend.relays[0].GetHeaderResponse.Capella.Signature = phase0.BLSSignature{} rr := backend.request(t, http.MethodGet, path, nil) require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) diff --git a/server/types.go b/server/types.go deleted file mode 100644 index 59e7abdd..00000000 --- a/server/types.go +++ /dev/null @@ -1,212 +0,0 @@ -package server - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/attestantio/go-builder-client/api/capella" - "github.com/attestantio/go-builder-client/spec" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/flashbots/go-boost-utils/types" -) - -var errNoResponse = errors.New("no response") - -// wrapper for backwards compatible capella types - -type GetHeaderResponse struct { - Bellatrix *types.GetHeaderResponse - Capella *spec.VersionedSignedBuilderBid -} - -func (r *GetHeaderResponse) UnmarshalJSON(data []byte) error { - var err error - - var capella spec.VersionedSignedBuilderBid - err = json.Unmarshal(data, &capella) - if err == nil && capella.Capella != nil { - r.Capella = &capella - return nil - } - - var bellatrix types.GetHeaderResponse - err = json.Unmarshal(data, &bellatrix) - if err != nil { - return err - } - - r.Bellatrix = &bellatrix - - return nil -} - -func (r *GetHeaderResponse) MarshalJSON() ([]byte, error) { - if r.Capella != nil { - return json.Marshal(r.Capella) - } - - if r.Bellatrix != nil { - return json.Marshal(r.Bellatrix) - } - - return nil, errNoResponse -} - -func (r *GetHeaderResponse) IsInvalid() bool { - if r.Bellatrix != nil { - return r.Bellatrix.Data == nil || r.Bellatrix.Data.Message == nil || r.Bellatrix.Data.Message.Header == nil || r.Bellatrix.Data.Message.Header.BlockHash == nilHash - } - - if r.Capella != nil { - return r.Capella.Capella == nil || r.Capella.Capella.Message == nil || r.Capella.Capella.Message.Header == nil || r.Capella.Capella.Message.Header.BlockHash == phase0.Hash32(nilHash) - } - - return true -} - -func (r *GetHeaderResponse) BlockHash() string { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Message.Header.BlockHash.String() - } - - if r.Capella != nil { - return r.Capella.Capella.Message.Header.BlockHash.String() - } - - return "" -} - -func (r *GetHeaderResponse) Value() *big.Int { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Message.Value.BigInt() - } - - if r.Capella != nil { - return r.Capella.Capella.Message.Value.ToBig() - } - - return nil -} - -func (r *GetHeaderResponse) BlockNumber() uint64 { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Message.Header.BlockNumber - } - - if r.Capella != nil { - return r.Capella.Capella.Message.Header.BlockNumber - } - - return 0 -} - -func (r *GetHeaderResponse) TransactionsRoot() string { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Message.Header.TransactionsRoot.String() - } - - if r.Capella != nil { - return r.Capella.Capella.Message.Header.TransactionsRoot.String() - } - - return "" -} - -func (r *GetHeaderResponse) Pubkey() string { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Message.Pubkey.String() - } - - if r.Capella != nil { - return r.Capella.Capella.Message.Pubkey.String() - } - - return "" -} - -func (r *GetHeaderResponse) Signature() []byte { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Signature[:] - } - - if r.Capella != nil { - return r.Capella.Capella.Signature[:] - } - - return nil -} - -func (r *GetHeaderResponse) Message() types.HashTreeRoot { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Message - } - - if r.Capella != nil { - return r.Capella.Capella.Message - } - - return nil -} - -func (r *GetHeaderResponse) ParentHash() string { - if r.Bellatrix != nil { - return r.Bellatrix.Data.Message.Header.ParentHash.String() - } - - if r.Capella != nil { - return r.Capella.Capella.Message.Header.ParentHash.String() - } - - return "" -} - -func (r *GetHeaderResponse) IsEmpty() bool { - return r.Bellatrix == nil && r.Capella == nil -} - -func (r *GetHeaderResponse) BuilderBid() *SignedBuilderBid { - if r.Bellatrix != nil { - return &SignedBuilderBid{Bellatrix: r.Bellatrix.Data} - } - if r.Capella != nil { - return &SignedBuilderBid{Capella: r.Capella.Capella} - } - return nil -} - -type SignedBuilderBid struct { - Bellatrix *types.SignedBuilderBid - Capella *capella.SignedBuilderBid -} - -func (r *SignedBuilderBid) UnmarshalJSON(data []byte) error { - var err error - var bellatrix types.SignedBuilderBid - err = json.Unmarshal(data, &bellatrix) - if err == nil { - r.Bellatrix = &bellatrix - return nil - } - - var capella capella.SignedBuilderBid - err = json.Unmarshal(data, &capella) - if err != nil { - return err - } - - r.Capella = &capella - return nil -} - -func (r *SignedBuilderBid) MarshalJSON() ([]byte, error) { - if r.Bellatrix != nil { - return json.Marshal(r.Bellatrix) - } - - if r.Capella != nil { - return json.Marshal(r.Capella) - } - - return nil, errNoResponse -} diff --git a/server/types_test.go b/server/types_test.go deleted file mode 100644 index 84c32d50..00000000 --- a/server/types_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package server - -import ( - "bytes" - "encoding/json" - "os" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGetHeaderResponseJSON(t *testing.T) { - testHeaderFiles := []string{ - "../testdata/signed-builder-bid-bellatrix.json", - "../testdata/signed-builder-bid-capella.json", - } - - for _, fn := range testHeaderFiles { - t.Run(fn, func(t *testing.T) { - jsonBytes, err := os.ReadFile(fn) - require.NoError(t, err) - - getHeaderResponse := new(GetHeaderResponse) - err = DecodeJSON(bytes.NewReader(jsonBytes), getHeaderResponse) - - require.NoError(t, err) - o := new(bytes.Buffer) - err = json.NewEncoder(o).Encode(&getHeaderResponse) - require.NoError(t, err) - - i := new(bytes.Buffer) - err = json.Compact(i, jsonBytes) - require.NoError(t, err) - - require.Equal(t, strings.ToUpper(i.String()), strings.ToUpper(strings.TrimSpace(o.String()))) - }) - } -} diff --git a/server/utils.go b/server/utils.go index 2ba15f90..c426b87b 100644 --- a/server/utils.go +++ b/server/utils.go @@ -13,14 +13,17 @@ import ( "strings" "time" + "github.com/attestantio/go-builder-client/spec" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/trie" + "github.com/flashbots/go-boost-utils/bls" boostTypes "github.com/flashbots/go-boost-utils/types" "github.com/flashbots/mev-boost/config" + "github.com/holiman/uint256" "github.com/sirupsen/logrus" ) @@ -169,10 +172,10 @@ func GetURI(url *url.URL, path string) string { // bidResp are entries in the bids cache type bidResp struct { - t time.Time - response GetHeaderResponse - blockHash string - relays []RelayEntry + t time.Time + response spec.VersionedSignedBuilderBid + bidInfo bidInfo + relays []RelayEntry } // bidRespKey is used as key for the bids cache @@ -181,6 +184,16 @@ type bidRespKey struct { blockHash string } +// bidInfo is used to store bid response fields for logging and validation +type bidInfo struct { + blockHash phase0.Hash32 + parentHash phase0.Hash32 + pubkey phase0.BLSPubKey + blockNumber uint64 + txRoot phase0.Root + value *uint256.Int +} + func httpClientDisallowRedirects(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } @@ -250,3 +263,57 @@ func executionPayloadToBlockHeader(payload *capella.ExecutionPayload) (*types.He WithdrawalsHash: &withdrawalsHash, }, nil } + +func parseBidInfo(bid *spec.VersionedSignedBuilderBid) (bidInfo, error) { + blockHash, err := bid.BlockHash() + if err != nil { + return bidInfo{}, err + } + parentHash, err := bid.ParentHash() + if err != nil { + return bidInfo{}, err + } + pubkey, err := bid.Builder() + if err != nil { + return bidInfo{}, err + } + blockNumber, err := bid.BlockNumber() + if err != nil { + return bidInfo{}, err + } + txRoot, err := bid.TransactionsRoot() + if err != nil { + return bidInfo{}, err + } + value, err := bid.Value() + if err != nil { + return bidInfo{}, err + } + bidInfo := bidInfo{ + blockHash: blockHash, + parentHash: parentHash, + pubkey: pubkey, + blockNumber: blockNumber, + txRoot: txRoot, + value: value, + } + return bidInfo, nil +} + +func checkRelaySignature(bid *spec.VersionedSignedBuilderBid, domain boostTypes.Domain, pubKey boostTypes.PublicKey) (bool, error) { + root, err := bid.MessageHashTreeRoot() + if err != nil { + return false, err + } + sig, err := bid.Signature() + if err != nil { + return false, err + } + signingData := boostTypes.SigningData{Root: boostTypes.Root(root), Domain: domain} + msg, err := signingData.HashTreeRoot() + if err != nil { + return false, err + } + + return bls.VerifySignatureBytes(msg[:], sig[:], pubKey[:]) +}