Skip to content

Commit 5f634cf

Browse files
authored
Split functionality.go into get_{header,payload}.go (#730)
1 parent 2cd4e9a commit 5f634cf

File tree

2 files changed

+191
-178
lines changed

2 files changed

+191
-178
lines changed

server/get_header.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package server
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"sync"
8+
"time"
9+
10+
builderSpec "github.com/attestantio/go-builder-client/spec"
11+
"github.com/attestantio/go-eth2-client/spec/phase0"
12+
"github.com/flashbots/mev-boost/config"
13+
"github.com/flashbots/mev-boost/server/types"
14+
"github.com/google/uuid"
15+
"github.com/sirupsen/logrus"
16+
)
17+
18+
// getHeader requests a bid from each relay and returns the most profitable one
19+
func (m *BoostService) getHeader(log *logrus.Entry, ua UserAgent, slot phase0.Slot, pubkey, parentHashHex string) (bidResp, error) {
20+
// Ensure arguments are valid
21+
if len(pubkey) != 98 {
22+
return bidResp{}, errInvalidPubkey
23+
}
24+
if len(parentHashHex) != 66 {
25+
return bidResp{}, errInvalidHash
26+
}
27+
28+
// Make sure we have a uid for this slot
29+
m.slotUIDLock.Lock()
30+
if m.slotUID.slot < slot {
31+
m.slotUID.slot = slot
32+
m.slotUID.uid = uuid.New()
33+
}
34+
slotUID := m.slotUID.uid
35+
m.slotUIDLock.Unlock()
36+
log = log.WithField("slotUID", slotUID)
37+
38+
// Log how late into the slot the request starts
39+
slotStartTimestamp := m.genesisTime + uint64(slot)*config.SlotTimeSec
40+
msIntoSlot := uint64(time.Now().UTC().UnixMilli()) - slotStartTimestamp*1000
41+
log.WithFields(logrus.Fields{
42+
"genesisTime": m.genesisTime,
43+
"slotTimeSec": config.SlotTimeSec,
44+
"msIntoSlot": msIntoSlot,
45+
}).Infof("getHeader request start - %d milliseconds into slot %d", msIntoSlot, slot)
46+
47+
// Add request headers
48+
headers := map[string]string{
49+
HeaderKeySlotUID: slotUID.String(),
50+
HeaderStartTimeUnixMS: fmt.Sprintf("%d", time.Now().UTC().UnixMilli()),
51+
}
52+
53+
var (
54+
mu sync.Mutex
55+
wg sync.WaitGroup
56+
57+
// The final response, containing the highest bid (if any)
58+
result = bidResp{}
59+
60+
// Relays that sent the bid for a specific blockHash
61+
relays = make(map[BlockHashHex][]types.RelayEntry)
62+
)
63+
64+
// Request a bid from each relay
65+
for _, relay := range m.relays {
66+
wg.Add(1)
67+
go func(relay types.RelayEntry) {
68+
defer wg.Done()
69+
70+
// Build the request URL
71+
url := relay.GetURI(fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", slot, parentHashHex, pubkey))
72+
log := log.WithField("url", url)
73+
74+
// Send the get bid request to the relay
75+
bid := new(builderSpec.VersionedSignedBuilderBid)
76+
code, err := SendHTTPRequest(context.Background(), m.httpClientGetHeader, http.MethodGet, url, ua, headers, nil, bid)
77+
if err != nil {
78+
log.WithError(err).Warn("error making request to relay")
79+
return
80+
}
81+
if code == http.StatusNoContent {
82+
log.Debug("no-content response")
83+
return
84+
}
85+
86+
// Skip if bid is empty
87+
if bid.IsEmpty() {
88+
return
89+
}
90+
91+
// Getting the bid info will check if there are missing fields in the response
92+
bidInfo, err := parseBidInfo(bid)
93+
if err != nil {
94+
log.WithError(err).Warn("error parsing bid info")
95+
return
96+
}
97+
98+
// Ignore bids with an empty block
99+
if bidInfo.blockHash == nilHash {
100+
log.Warn("relay responded with empty block hash")
101+
return
102+
}
103+
104+
// Add some info about the bid to the logger
105+
valueEth := weiBigIntToEthBigFloat(bidInfo.value.ToBig())
106+
log = log.WithFields(logrus.Fields{
107+
"blockNumber": bidInfo.blockNumber,
108+
"blockHash": bidInfo.blockHash.String(),
109+
"txRoot": bidInfo.txRoot.String(),
110+
"value": valueEth.Text('f', 18),
111+
})
112+
113+
// Ensure the bid uses the correct public key
114+
if relay.PublicKey.String() != bidInfo.pubkey.String() {
115+
log.Errorf("bid pubkey mismatch. expected: %s - got: %s", relay.PublicKey.String(), bidInfo.pubkey.String())
116+
return
117+
}
118+
119+
// Verify the relay signature in the relay response
120+
if !config.SkipRelaySignatureCheck {
121+
ok, err := checkRelaySignature(bid, m.builderSigningDomain, relay.PublicKey)
122+
if err != nil {
123+
log.WithError(err).Error("error verifying relay signature")
124+
return
125+
}
126+
if !ok {
127+
log.Error("failed to verify relay signature")
128+
return
129+
}
130+
}
131+
132+
// Verify response coherence with proposer's input data
133+
if bidInfo.parentHash.String() != parentHashHex {
134+
log.WithFields(logrus.Fields{
135+
"originalParentHash": parentHashHex,
136+
"responseParentHash": bidInfo.parentHash.String(),
137+
}).Error("proposer and relay parent hashes are not the same")
138+
return
139+
}
140+
141+
// Ignore bids with 0 value
142+
isZeroValue := bidInfo.value.IsZero()
143+
isEmptyListTxRoot := bidInfo.txRoot.String() == "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1"
144+
if isZeroValue || isEmptyListTxRoot {
145+
log.Warn("ignoring bid with 0 value")
146+
return
147+
}
148+
149+
log.Debug("bid received")
150+
151+
// Skip if value is lower than the minimum bid
152+
if bidInfo.value.CmpBig(m.relayMinBid.BigInt()) == -1 {
153+
log.Debug("ignoring bid below min-bid value")
154+
return
155+
}
156+
157+
mu.Lock()
158+
defer mu.Unlock()
159+
160+
// Remember which relays delivered which bids (multiple relays might deliver the top bid)
161+
relays[BlockHashHex(bidInfo.blockHash.String())] = append(relays[BlockHashHex(bidInfo.blockHash.String())], relay)
162+
163+
// Compare the bid with already known top bid (if any)
164+
if !result.response.IsEmpty() {
165+
valueDiff := bidInfo.value.Cmp(result.bidInfo.value)
166+
if valueDiff == -1 {
167+
// The current bid is less profitable than already known one
168+
return
169+
} else if valueDiff == 0 {
170+
// The current bid is equally profitable as already known one
171+
// Use hash as tiebreaker
172+
previousBidBlockHash := result.bidInfo.blockHash
173+
if bidInfo.blockHash.String() >= previousBidBlockHash.String() {
174+
return
175+
}
176+
}
177+
}
178+
179+
// Use this relay's response as mev-boost response because it's most profitable
180+
log.Debug("new best bid")
181+
result.response = *bid
182+
result.bidInfo = bidInfo
183+
result.t = time.Now()
184+
}(relay)
185+
}
186+
wg.Wait()
187+
188+
// Set the winning relays before returning
189+
result.relays = relays[BlockHashHex(result.bidInfo.blockHash.String())]
190+
return result, nil
191+
}
Lines changed: 0 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ import (
55
"errors"
66
"fmt"
77
"net/http"
8-
"sync"
98
"sync/atomic"
109
"time"
1110

1211
builderApi "github.com/attestantio/go-builder-client/api"
1312
denebApi "github.com/attestantio/go-builder-client/api/deneb"
14-
builderSpec "github.com/attestantio/go-builder-client/spec"
1513
eth2ApiV1Bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix"
1614
eth2ApiV1Capella "github.com/attestantio/go-eth2-client/api/v1/capella"
1715
eth2ApiV1Deneb "github.com/attestantio/go-eth2-client/api/v1/deneb"
@@ -22,7 +20,6 @@ import (
2220
"github.com/flashbots/mev-boost/config"
2321
"github.com/flashbots/mev-boost/server/params"
2422
"github.com/flashbots/mev-boost/server/types"
25-
"github.com/google/uuid"
2623
"github.com/sirupsen/logrus"
2724
)
2825

@@ -315,178 +312,3 @@ func blockHash[P Payload](payload P) phase0.Hash32 {
315312
func bidKey(slot phase0.Slot, blockHash phase0.Hash32) string {
316313
return fmt.Sprintf("%v%v", slot, blockHash)
317314
}
318-
319-
// getHeader requests a bid from each relay and returns the most profitable one
320-
func (m *BoostService) getHeader(log *logrus.Entry, ua UserAgent, slot phase0.Slot, pubkey, parentHashHex string) (bidResp, error) {
321-
// Ensure arguments are valid
322-
if len(pubkey) != 98 {
323-
return bidResp{}, errInvalidPubkey
324-
}
325-
if len(parentHashHex) != 66 {
326-
return bidResp{}, errInvalidHash
327-
}
328-
329-
// Make sure we have a uid for this slot
330-
m.slotUIDLock.Lock()
331-
if m.slotUID.slot < slot {
332-
m.slotUID.slot = slot
333-
m.slotUID.uid = uuid.New()
334-
}
335-
slotUID := m.slotUID.uid
336-
m.slotUIDLock.Unlock()
337-
log = log.WithField("slotUID", slotUID)
338-
339-
// Log how late into the slot the request starts
340-
slotStartTimestamp := m.genesisTime + uint64(slot)*config.SlotTimeSec
341-
msIntoSlot := uint64(time.Now().UTC().UnixMilli()) - slotStartTimestamp*1000
342-
log.WithFields(logrus.Fields{
343-
"genesisTime": m.genesisTime,
344-
"slotTimeSec": config.SlotTimeSec,
345-
"msIntoSlot": msIntoSlot,
346-
}).Infof("getHeader request start - %d milliseconds into slot %d", msIntoSlot, slot)
347-
348-
// Add request headers
349-
headers := map[string]string{
350-
HeaderKeySlotUID: slotUID.String(),
351-
HeaderStartTimeUnixMS: fmt.Sprintf("%d", time.Now().UTC().UnixMilli()),
352-
}
353-
354-
var (
355-
mu sync.Mutex
356-
wg sync.WaitGroup
357-
358-
// The final response, containing the highest bid (if any)
359-
result = bidResp{}
360-
361-
// Relays that sent the bid for a specific blockHash
362-
relays = make(map[BlockHashHex][]types.RelayEntry)
363-
)
364-
365-
// Request a bid from each relay
366-
for _, relay := range m.relays {
367-
wg.Add(1)
368-
go func(relay types.RelayEntry) {
369-
defer wg.Done()
370-
371-
// Build the request URL
372-
url := relay.GetURI(fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", slot, parentHashHex, pubkey))
373-
log := log.WithField("url", url)
374-
375-
// Send the get bid request to the relay
376-
bid := new(builderSpec.VersionedSignedBuilderBid)
377-
code, err := SendHTTPRequest(context.Background(), m.httpClientGetHeader, http.MethodGet, url, ua, headers, nil, bid)
378-
if err != nil {
379-
log.WithError(err).Warn("error making request to relay")
380-
return
381-
}
382-
if code == http.StatusNoContent {
383-
log.Debug("no-content response")
384-
return
385-
}
386-
387-
// Skip if bid is empty
388-
if bid.IsEmpty() {
389-
return
390-
}
391-
392-
// Getting the bid info will check if there are missing fields in the response
393-
bidInfo, err := parseBidInfo(bid)
394-
if err != nil {
395-
log.WithError(err).Warn("error parsing bid info")
396-
return
397-
}
398-
399-
// Ignore bids with an empty block
400-
if bidInfo.blockHash == nilHash {
401-
log.Warn("relay responded with empty block hash")
402-
return
403-
}
404-
405-
// Add some info about the bid to the logger
406-
valueEth := weiBigIntToEthBigFloat(bidInfo.value.ToBig())
407-
log = log.WithFields(logrus.Fields{
408-
"blockNumber": bidInfo.blockNumber,
409-
"blockHash": bidInfo.blockHash.String(),
410-
"txRoot": bidInfo.txRoot.String(),
411-
"value": valueEth.Text('f', 18),
412-
})
413-
414-
// Ensure the bid uses the correct public key
415-
if relay.PublicKey.String() != bidInfo.pubkey.String() {
416-
log.Errorf("bid pubkey mismatch. expected: %s - got: %s", relay.PublicKey.String(), bidInfo.pubkey.String())
417-
return
418-
}
419-
420-
// Verify the relay signature in the relay response
421-
if !config.SkipRelaySignatureCheck {
422-
ok, err := checkRelaySignature(bid, m.builderSigningDomain, relay.PublicKey)
423-
if err != nil {
424-
log.WithError(err).Error("error verifying relay signature")
425-
return
426-
}
427-
if !ok {
428-
log.Error("failed to verify relay signature")
429-
return
430-
}
431-
}
432-
433-
// Verify response coherence with proposer's input data
434-
if bidInfo.parentHash.String() != parentHashHex {
435-
log.WithFields(logrus.Fields{
436-
"originalParentHash": parentHashHex,
437-
"responseParentHash": bidInfo.parentHash.String(),
438-
}).Error("proposer and relay parent hashes are not the same")
439-
return
440-
}
441-
442-
// Ignore bids with 0 value
443-
isZeroValue := bidInfo.value.IsZero()
444-
isEmptyListTxRoot := bidInfo.txRoot.String() == "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1"
445-
if isZeroValue || isEmptyListTxRoot {
446-
log.Warn("ignoring bid with 0 value")
447-
return
448-
}
449-
450-
log.Debug("bid received")
451-
452-
// Skip if value is lower than the minimum bid
453-
if bidInfo.value.CmpBig(m.relayMinBid.BigInt()) == -1 {
454-
log.Debug("ignoring bid below min-bid value")
455-
return
456-
}
457-
458-
mu.Lock()
459-
defer mu.Unlock()
460-
461-
// Remember which relays delivered which bids (multiple relays might deliver the top bid)
462-
relays[BlockHashHex(bidInfo.blockHash.String())] = append(relays[BlockHashHex(bidInfo.blockHash.String())], relay)
463-
464-
// Compare the bid with already known top bid (if any)
465-
if !result.response.IsEmpty() {
466-
valueDiff := bidInfo.value.Cmp(result.bidInfo.value)
467-
if valueDiff == -1 {
468-
// The current bid is less profitable than already known one
469-
return
470-
} else if valueDiff == 0 {
471-
// The current bid is equally profitable as already known one
472-
// Use hash as tiebreaker
473-
previousBidBlockHash := result.bidInfo.blockHash
474-
if bidInfo.blockHash.String() >= previousBidBlockHash.String() {
475-
return
476-
}
477-
}
478-
}
479-
480-
// Use this relay's response as mev-boost response because it's most profitable
481-
log.Debug("new best bid")
482-
result.response = *bid
483-
result.bidInfo = bidInfo
484-
result.t = time.Now()
485-
}(relay)
486-
}
487-
wg.Wait()
488-
489-
// Set the winning relays before returning
490-
result.relays = relays[BlockHashHex(result.bidInfo.blockHash.String())]
491-
return result, nil
492-
}

0 commit comments

Comments
 (0)