Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: privileged-builders #656

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var flags = []cli.Flag{
timeoutGetPayloadFlag,
timeoutRegValFlag,
maxRetriesFlag,
privilegedBuildersFlag,
}

var (
Expand Down Expand Up @@ -171,4 +172,10 @@ var (
Value: 5,
Category: RelayCategory,
}
privilegedBuildersFlag = &cli.StringSliceFlag{
Name: "privileged-builder",
Sources: cli.EnvVars("PRIVILEGED_BUILDER"),
Usage: "relay username/pubkey - single entry or comma-separated list",
Category: RelayCategory,
}
)
30 changes: 22 additions & 8 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,17 @@ func start(_ context.Context, cmd *cli.Command) error {
}

var (
genesisForkVersion, genesisTime = setupGenesis(cmd)
relays, monitors, minBid, relayCheck = setupRelays(cmd)
listenAddr = cmd.String(addrFlag.Name)
genesisForkVersion, genesisTime = setupGenesis(cmd)
relays, monitors, privileged, minBid, relayCheck = setupRelays(cmd)
listenAddr = cmd.String(addrFlag.Name)
)

opts := server.BoostServiceOpts{
Log: log,
ListenAddr: listenAddr,
Relays: relays,
RelayMonitors: monitors,
PrivilegedBuilders: privileged,
GenesisForkVersionHex: genesisForkVersion,
GenesisTime: genesisTime,
RelayCheck: relayCheck,
Expand All @@ -97,11 +98,12 @@ func start(_ context.Context, cmd *cli.Command) error {
return service.StartHTTPServer()
}

func setupRelays(cmd *cli.Command) (relayList, relayMonitorList, types.U256Str, bool) {
func setupRelays(cmd *cli.Command) (relayList, relayMonitorList, privilegedBuilderList, types.U256Str, bool) {
// For backwards compatibility with the -relays flag.
var (
relays relayList
monitors relayMonitorList
relays relayList
monitors relayMonitorList
privileged privilegedBuilderList
)
if cmd.IsSet(relaysFlag.Name) {
relayURLs := cmd.StringSlice(relaysFlag.Name)
Expand All @@ -117,9 +119,20 @@ func setupRelays(cmd *cli.Command) (relayList, relayMonitorList, types.U256Str,
if len(relays) == 0 {
log.Fatal("no relays specified")
}

if cmd.IsSet(privilegedBuildersFlag.Name) {
privilegedBuilders := cmd.StringSlice(privilegedBuildersFlag.Name)
for _, builder := range privilegedBuilders {
if err := privileged.Set(builder); err != nil {
log.WithError(err).WithField("privilegedBuilder", builder).Fatal("Invalid privileged builder")
}
}
}

log.Infof("using %d relays", len(relays))
for index, relay := range relays {
log.Infof("relay #%d: %s", index+1, relay.String())
isPrivileged := privileged.Contains(relay.PublicKey)
log.Infof("relay #%d: %s, privileged %t", index+1, relay.String(), isPrivileged)
}

// For backwards compatibility with the -relay-monitors flag.
Expand Down Expand Up @@ -148,7 +161,8 @@ func setupRelays(cmd *cli.Command) (relayList, relayMonitorList, types.U256Str,
if relayMinBidWei.BigInt().Sign() > 0 {
log.Infof("Min bid set to %v eth (%v wei)", cmd.Float(minBidFlag.Name), relayMinBidWei)
}
return relays, monitors, *relayMinBidWei, cmd.Bool(relayCheckFlag.Name)

return relays, monitors, privileged, *relayMinBidWei, cmd.Bool(relayCheckFlag.Name)
}

func setupGenesis(cmd *cli.Command) (string, uint64) {
Expand Down
34 changes: 34 additions & 0 deletions cli/types.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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
}
118 changes: 82 additions & 36 deletions server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type BoostServiceOpts struct {
ListenAddr string
Relays []types.RelayEntry
RelayMonitors []*url.URL
PrivilegedBuilders []phase0.BLSPubKey
GenesisForkVersionHex string
GenesisTime uint64
RelayCheck bool
Expand All @@ -73,14 +74,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
Expand All @@ -107,15 +109,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,
log: opts.Log,
relayCheck: opts.RelayCheck,
relayMinBid: opts.RelayMinBid,
privilegedBuilders: opts.PrivilegedBuilders,
genesisTime: opts.GenesisTime,
bids: make(map[bidRespKey]bidResp),
slotUID: &slotUID{},

builderSigningDomain: builderSigningDomain,
httpClientGetHeader: http.Client{
Expand Down Expand Up @@ -289,7 +292,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"]
Expand Down Expand Up @@ -346,6 +349,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
Expand Down Expand Up @@ -441,35 +445,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())]
Expand All @@ -479,6 +494,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
Expand All @@ -491,6 +507,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) processDenebPayload(w http.ResponseWriter, req *http.Request, log *logrus.Entry, blindedBlock *eth2ApiV1Deneb.SignedBlindedBeaconBlock) {
// Get the currentSlotUID for this slot
currentSlotUID := ""
Expand Down Expand Up @@ -688,3 +725,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
}