Skip to content

Commit

Permalink
add deneb path
Browse files Browse the repository at this point in the history
  • Loading branch information
avalonche authored and metachris committed Jan 11, 2024
1 parent bcfd1db commit f5ac1bd
Show file tree
Hide file tree
Showing 8 changed files with 667 additions and 46 deletions.
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ linters-settings:
disable:
- fieldalignment
- shadow
gomoddirectives:
replace-allow-list:
- github.com/attestantio/go-eth2-client
- github.com/attestantio/go-builder-client

output:
print-issued-lines: true
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/attestantio/go-builder-client => github.com/avalonche/go-builder-client v0.0.0-20230727154356-9a77badf0423

replace github.com/attestantio/go-eth2-client => github.com/avalonche/go-eth2-client v0.0.0-20230727154722-91c9a241729e
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ 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.4.2 h1:EycfAFqQV+ooc2z6hmTsbuH4TCLknr0aO0nHLHLMpJM=
github.com/attestantio/go-builder-client v0.4.2/go.mod h1:e02i/WO4fjs3/u9oIZEjiC8CK1Qyxy4cpiMMGKx4VqQ=
github.com/attestantio/go-eth2-client v0.19.8 h1:7hn5X1MohW+wpfnwOpysPpjSktZwTB0A2ClohOCgZLI=
github.com/attestantio/go-eth2-client v0.19.8/go.mod h1:mZve1kV9Ctj0I1HH9gdg+MnI8lZ+Cb2EktEtOYrBlsM=
github.com/avalonche/go-builder-client v0.0.0-20230727154356-9a77badf0423 h1:JWWVNPwWVThSmcfzKBjRbglwe4c6eEYquALAISClK0U=
github.com/avalonche/go-builder-client v0.0.0-20230727154356-9a77badf0423/go.mod h1:8NZx9L/rC7nLhSMbbVR6PXr37tC28wTE4u65n+Pa3BQ=
github.com/avalonche/go-eth2-client v0.0.0-20230727154722-91c9a241729e h1:4bLKeIfXtiYOuEJyZuXeWWdRwfArYRd7UP4PBJZUVOo=
github.com/avalonche/go-eth2-client v0.0.0-20230727154722-91c9a241729e/go.mod h1:KSVlZSW1A3jUg5H8O89DLtqxgJprRfTtI7k89fLdhu0=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down
45 changes: 24 additions & 21 deletions server/mock_relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import (
"time"

"github.com/attestantio/go-builder-client/api"
apibellatrix "github.com/attestantio/go-builder-client/api/bellatrix"
apicapella "github.com/attestantio/go-builder-client/api/capella"
apideneb "github.com/attestantio/go-builder-client/api/deneb"
apiv1 "github.com/attestantio/go-builder-client/api/v1"
"github.com/attestantio/go-builder-client/spec"
consensusspec "github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
"github.com/attestantio/go-eth2-client/spec/capella"
"github.com/attestantio/go-eth2-client/spec/deneb"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/flashbots/go-boost-utils/bls"
Expand Down Expand Up @@ -60,7 +60,7 @@ type mockRelay struct {

// Default responses placeholders, used if overrider does not exist
GetHeaderResponse *spec.VersionedSignedBuilderBid
GetPayloadResponse *api.VersionedExecutionPayload
GetPayloadResponse *api.VersionedSubmitBlindedBlockResponse

// Server section
Server *httptest.Server
Expand Down Expand Up @@ -168,12 +168,13 @@ func (m *mockRelay) defaultHandleRegisterValidator(w http.ResponseWriter, req *h
// method
func (m *mockRelay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, publicKey string, version consensusspec.DataVersion) *spec.VersionedSignedBuilderBid {
switch version {
case consensusspec.DataVersionBellatrix:
case consensusspec.DataVersionCapella:
// Fill the payload with custom values.
message := &apibellatrix.BuilderBid{
Header: &bellatrix.ExecutionPayloadHeader{
BlockHash: _HexToHash(blockHash),
ParentHash: _HexToHash(parentHash),
message := &apicapella.BuilderBid{
Header: &capella.ExecutionPayloadHeader{
BlockHash: _HexToHash(blockHash),
ParentHash: _HexToHash(parentHash),
WithdrawalsRoot: phase0.Root{},
},
Value: uint256.NewInt(value),
Pubkey: _HexToPubkey(publicKey),
Expand All @@ -185,35 +186,36 @@ func (m *mockRelay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, p

return &spec.VersionedSignedBuilderBid{
Version: consensusspec.DataVersionCapella,
Bellatrix: &apibellatrix.SignedBuilderBid{
Capella: &apicapella.SignedBuilderBid{
Message: message,
Signature: signature,
},
}
case consensusspec.DataVersionCapella:
// Fill the payload with custom values.
message := &apicapella.BuilderBid{
Header: &capella.ExecutionPayloadHeader{
case consensusspec.DataVersionDeneb:
message := &apideneb.BuilderBid{
Header: &deneb.ExecutionPayloadHeader{
BlockHash: _HexToHash(blockHash),
ParentHash: _HexToHash(parentHash),
WithdrawalsRoot: phase0.Root{},
BaseFeePerGas: uint256.NewInt(0),
},
Value: uint256.NewInt(value),
Pubkey: _HexToPubkey(publicKey),
Value: uint256.NewInt(value),
Pubkey: _HexToPubkey(publicKey),
BlindedBlobsBundle: &apideneb.BlindedBlobsBundle{},
}

// Sign the message.
signature, err := ssz.SignMessage(message, ssz.DomainBuilder, m.secretKey)
require.NoError(m.t, err)

return &spec.VersionedSignedBuilderBid{
Version: consensusspec.DataVersionCapella,
Capella: &apicapella.SignedBuilderBid{
Version: consensusspec.DataVersionDeneb,
Deneb: &apideneb.SignedBuilderBid{
Message: message,
Signature: signature,
},
}
case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionDeneb:
case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix:
return nil
}
return nil
Expand Down Expand Up @@ -257,9 +259,9 @@ func (m *mockRelay) defaultHandleGetHeader(w http.ResponseWriter) {

// MakeGetPayloadResponse is used to create the default or can be used to create a custom response to the getPayload
// method
func (m *mockRelay) MakeGetPayloadResponse(parentHash, blockHash, feeRecipient string, blockNumber uint64) *api.VersionedExecutionPayload {
return &api.VersionedExecutionPayload{
Version: consensusspec.DataVersionCapella,
func (m *mockRelay) MakeGetPayloadResponse(parentHash, blockHash, feeRecipient string, blockNumber uint64, version consensusspec.DataVersion) *api.VersionedSubmitBlindedBlockResponse {
return &api.VersionedSubmitBlindedBlockResponse{
Version: version,
Capella: &capella.ExecutionPayload{
ParentHash: _HexToHash(parentHash),
BlockHash: _HexToHash(blockHash),
Expand Down Expand Up @@ -294,6 +296,7 @@ func (m *mockRelay) defaultHandleGetPayload(w http.ResponseWriter) {
"0x534809bd2b6832edff8d8ce4cb0e50068804fd1ef432c8362ad708a74fdc0e46",
"0xdb65fEd33dc262Fe09D9a2Ba8F80b329BA25f941",
12345,
consensusspec.DataVersionCapella,
)

if m.GetPayloadResponse != nil {
Expand Down
189 changes: 180 additions & 9 deletions server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import (
"github.com/attestantio/go-builder-client/spec"
apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix"
"github.com/attestantio/go-eth2-client/api/v1/capella"
"github.com/attestantio/go-eth2-client/api/v1/deneb"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/flashbots/go-boost-utils/ssz"
"github.com/flashbots/go-boost-utils/types"
"github.com/flashbots/go-boost-utils/utils"
"github.com/flashbots/go-utils/httplogger"
"github.com/flashbots/mev-boost/config"
"github.com/google/uuid"
Expand Down Expand Up @@ -497,7 +499,7 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request)
log.WithFields(logrus.Fields{
"blockHash": result.bidInfo.blockHash.String(),
"blockNumber": result.bidInfo.blockNumber,
"txRoot": result.bidInfo.txRoot,
"txRoot": result.bidInfo.txRoot.String(),
"value": valueEth.Text('f', 18),
"relays": strings.Join(RelayEntriesToStrings(result.relays), ", "),
}).Info("best bid")
Expand Down Expand Up @@ -568,7 +570,7 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re
// Prepare for requests
var wg sync.WaitGroup
var mu sync.Mutex
result := new(api.VersionedExecutionPayload)
result := new(api.VersionedSubmitBlindedBlockResponse)

// Prepare the request context, which will be cancelled after the first successful response from a relay
requestCtx, requestCtxCancel := context.WithCancel(context.Background())
Expand All @@ -582,7 +584,7 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re
log := log.WithField("url", url)
log.Debug("calling getPayload")

responsePayload := new(api.VersionedExecutionPayload)
responsePayload := new(api.VersionedSubmitBlindedBlockResponse)
_, err := SendHTTPRequestWithRetries(requestCtx, m.httpClientGetPayload, http.MethodPost, url, ua, headers, payload, responsePayload, m.requestMaxRetries, log)
if err != nil {
if errors.Is(requestCtx.Err(), context.Canceled) {
Expand All @@ -593,7 +595,7 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re
return
}

if responsePayload.Capella == nil || responsePayload.Capella.BlockHash == nilHash {
if getPayloadResponseIsEmpty(responsePayload) {
log.Error("response with empty data!")
return
}
Expand All @@ -607,7 +609,10 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re
}

// Ensure the response blockhash matches the response block
calculatedBlockHash, err := ComputeBlockHash(responsePayload.Capella)
calculatedBlockHash, err := utils.ComputeBlockHash(&api.VersionedExecutionPayload{
Version: responsePayload.Version,
Capella: responsePayload.Capella,
})
if err != nil {
log.WithError(err).Error("could not calculate block hash")
} else if responsePayload.Capella.BlockHash != calculatedBlockHash {
Expand Down Expand Up @@ -646,6 +651,166 @@ func (m *BoostService) processCapellaPayload(w http.ResponseWriter, req *http.Re
m.respondOK(w, result)
}

func (m *BoostService) processDenebPayload(w http.ResponseWriter, req *http.Request, log *logrus.Entry, payload *deneb.SignedBlindedBlockContents, body []byte) {
if payload == nil || payload.SignedBlindedBlock == nil || payload.SignedBlindedBlobSidecars == nil {
log.WithField("body", string(body)).Error("missing parts of the request payload from the beacon-node")
m.respondError(w, http.StatusBadRequest, "missing parts of the payload")
return
}

blindedBlock := payload.SignedBlindedBlock
if blindedBlock.Message == nil || blindedBlock.Message.Body == nil || blindedBlock.Message.Body.ExecutionPayloadHeader == nil {
log.WithField("body", string(body)).Error("missing parts of the block from the beacon-node")
m.respondError(w, http.StatusBadRequest, "missing parts of the block")
return
}

blindedBlobs := payload.SignedBlindedBlobSidecars
if blindedBlobs == nil {
log.WithField("body", string(body)).Error("missing blobs bundle from the beacon-node")
m.respondError(w, http.StatusBadRequest, "missing blobs bundle")
return
}

// Get the slotUID for this slot
slotUID := ""
m.slotUIDLock.Lock()
if m.slotUID.slot == uint64(blindedBlock.Message.Slot) {
slotUID = m.slotUID.uid.String()
} else {
log.Warnf("latest slotUID is for slot %d rather than payload slot %d", m.slotUID.slot, blindedBlock.Message.Slot)
}
m.slotUIDLock.Unlock()

// Prepare logger
ua := UserAgent(req.Header.Get("User-Agent"))
log = log.WithFields(logrus.Fields{
"ua": ua,
"slot": blindedBlock.Message.Slot,
"blockHash": blindedBlock.Message.Body.ExecutionPayloadHeader.BlockHash.String(),
"parentHash": blindedBlock.Message.Body.ExecutionPayloadHeader.ParentHash.String(),
"slotUID": slotUID,
})

// Log how late into the slot the request starts
slotStartTimestamp := m.genesisTime + uint64(blindedBlock.Message.Slot)*config.SlotTimeSec
msIntoSlot := uint64(time.Now().UTC().UnixMilli()) - slotStartTimestamp*1000
log.WithFields(logrus.Fields{
"genesisTime": m.genesisTime,
"slotTimeSec": config.SlotTimeSec,
"msIntoSlot": msIntoSlot,
}).Infof("submitBlindedBlock request start - %d milliseconds into slot %d", msIntoSlot, blindedBlock.Message.Slot)

// Get the bid!
bidKey := bidRespKey{slot: uint64(blindedBlock.Message.Slot), blockHash: blindedBlock.Message.Body.ExecutionPayloadHeader.BlockHash.String()}
m.bidsLock.Lock()
originalBid := m.bids[bidKey]
m.bidsLock.Unlock()
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")
}

// Add request headers
headers := map[string]string{HeaderKeySlotUID: slotUID}

// Prepare for requests
var wg sync.WaitGroup
var mu sync.Mutex
result := new(api.VersionedSubmitBlindedBlockResponse)

// Prepare the request context, which will be cancelled after the first successful response from a relay
requestCtx, requestCtxCancel := context.WithCancel(context.Background())
defer requestCtxCancel()

for _, relay := range m.relays {
wg.Add(1)
go func(relay RelayEntry) {
defer wg.Done()
url := relay.GetURI(pathGetPayload)
log := log.WithField("url", url)
log.Debug("calling getPayload")

responsePayload := new(api.VersionedSubmitBlindedBlockResponse)
_, err := SendHTTPRequestWithRetries(requestCtx, m.httpClientGetPayload, http.MethodPost, url, ua, headers, payload, responsePayload, m.requestMaxRetries, log)
if err != nil {
if errors.Is(requestCtx.Err(), context.Canceled) {
log.Info("request was cancelled") // this is expected, if payload has already been received by another relay
} else {
log.WithError(err).Error("error making request to relay")
}
return
}

if getPayloadResponseIsEmpty(responsePayload) {
log.Error("response with empty data!")
return
}

payload := responsePayload.Deneb.ExecutionPayload
blobs := responsePayload.Deneb.BlobsBundle

// Ensure the response blockhash matches the request
if blindedBlock.Message.Body.ExecutionPayloadHeader.BlockHash != payload.BlockHash {
log.WithFields(logrus.Fields{
"responseBlockHash": responsePayload.Capella.BlockHash.String(),
}).Error("requestBlockHash does not equal responseBlockHash")
return
}

// Ensure the response blockhash matches the response block
calculatedBlockHash, err := utils.ComputeBlockHash(&api.VersionedExecutionPayload{
Version: responsePayload.Version,
Deneb: payload,
})
if err != nil {
log.WithError(err).Error("could not calculate block hash")
} else if payload.BlockHash != calculatedBlockHash {
log.WithFields(logrus.Fields{
"calculatedBlockHash": calculatedBlockHash.String(),
"responseBlockHash": payload.BlockHash.String(),
}).Error("responseBlockHash does not equal hash calculated from response block")
}

// Ensure that blobs are valid and matches the request
if len(blindedBlobs) != len(blobs.Blobs) {
log.WithFields(logrus.Fields{
"requestBlobs": len(blindedBlobs),
"responseBlobs": len(blobs.Blobs),
}).Error("requestBlobs length does not equal responseBlobs length")
return
}

// Lock before accessing the shared payload
mu.Lock()
defer mu.Unlock()

if requestCtx.Err() != nil { // request has been cancelled (or deadline exceeded)
return
}

// Received successful response. Now cancel other requests and return immediately
requestCtxCancel()
*result = *responsePayload
log.Info("received payload from relay")
}(relay)
}

// Wait for all requests to complete...
wg.Wait()

// If no payload has been received from relay, log loudly about withholding!
if getPayloadResponseIsEmpty(result) {
originRelays := RelayEntriesToStrings(originalBid.relays)
log.WithField("relaysWithBid", strings.Join(originRelays, ", ")).Error("no payload received from relay!")
m.respondError(w, http.StatusBadGateway, errNoSuccessfulRelayResponse.Error())
return
}

m.respondOK(w, result)
}

func (m *BoostService) handleGetPayload(w http.ResponseWriter, req *http.Request) {
log := m.log.WithField("method", "getPayload")
log.Debug("getPayload request starts")
Expand All @@ -659,13 +824,19 @@ func (m *BoostService) handleGetPayload(w http.ResponseWriter, req *http.Request
}

// Decode the body now
payload := new(capella.SignedBlindedBeaconBlock)
payload := new(deneb.SignedBlindedBlockContents)
if err := DecodeJSON(bytes.NewReader(body), payload); err != nil {
log.WithError(err).WithField("body", string(body)).Error("could not decode request payload from the beacon-node (signed blinded beacon block)")
m.respondError(w, http.StatusBadRequest, err.Error())
log.Debug("could not decode Deneb request payload, attempting to decode body into Capella payload")
payload := new(capella.SignedBlindedBeaconBlock)
if err := DecodeJSON(bytes.NewReader(body), payload); err != nil {
log.WithError(err).WithField("body", string(body)).Error("could not decode request payload from the beacon-node (signed blinded beacon block)")
m.respondError(w, http.StatusBadRequest, err.Error())
return
}
m.processCapellaPayload(w, req, log, payload, body)
return
}
m.processCapellaPayload(w, req, log, payload, body)
m.processDenebPayload(w, req, log, payload, body)
}

// CheckRelays sends a request to each one of the relays previously registered to get their status
Expand Down
Loading

0 comments on commit f5ac1bd

Please sign in to comment.