Skip to content

Commit 1217436

Browse files
server: re-add capella types (#724)
* server: re-add capella types * Remove unnecessary type arguments * Add missing verifyBlockhash for electra --------- Co-authored-by: Justin Traglia <jtraglia@pm.me>
1 parent ef1a706 commit 1217436

File tree

6 files changed

+385
-43
lines changed

6 files changed

+385
-43
lines changed

server/functionality.go

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
builderApi "github.com/attestantio/go-builder-client/api"
1313
denebApi "github.com/attestantio/go-builder-client/api/deneb"
1414
builderSpec "github.com/attestantio/go-builder-client/spec"
15+
eth2ApiV1Capella "github.com/attestantio/go-eth2-client/api/v1/capella"
1516
eth2ApiV1Deneb "github.com/attestantio/go-eth2-client/api/v1/deneb"
1617
eth2ApiV1Electra "github.com/attestantio/go-eth2-client/api/v1/electra"
1718
"github.com/attestantio/go-eth2-client/spec"
@@ -25,7 +26,8 @@ import (
2526
)
2627

2728
type Payload interface {
28-
*eth2ApiV1Deneb.SignedBlindedBeaconBlock |
29+
*eth2ApiV1Capella.SignedBlindedBeaconBlock |
30+
*eth2ApiV1Deneb.SignedBlindedBeaconBlock |
2931
*eth2ApiV1Electra.SignedBlindedBeaconBlock
3032
}
3133

@@ -39,8 +41,8 @@ var (
3941

4042
func processPayload[P Payload](m *BoostService, log *logrus.Entry, ua UserAgent, blindedBlock P) (*builderApi.VersionedSubmitBlindedBlockResponse, bidResp) {
4143
var (
42-
slot = slot[P](blindedBlock)
43-
blockHash = blockHash[P](blindedBlock)
44+
slot = slot(blindedBlock)
45+
blockHash = blockHash(blindedBlock)
4446
)
4547
// Get the currentSlotUID for this slot
4648
currentSlotUID := ""
@@ -53,7 +55,7 @@ func processPayload[P Payload](m *BoostService, log *logrus.Entry, ua UserAgent,
5355
m.slotUIDLock.Unlock()
5456

5557
// Prepare logger
56-
log = prepareLogger[P](log, blindedBlock, ua, currentSlotUID)
58+
log = prepareLogger(log, blindedBlock, ua, currentSlotUID)
5759

5860
// Log how late into the slot the request starts
5961
slotStartTimestamp := m.genesisTime + slot*config.SlotTimeSec
@@ -110,7 +112,7 @@ func processPayload[P Payload](m *BoostService, log *logrus.Entry, ua UserAgent,
110112
return
111113
}
112114

113-
if err := verifyPayload[P](blindedBlock, log, responsePayload); err != nil {
115+
if err := verifyPayload(blindedBlock, log, responsePayload); err != nil {
114116
return
115117
}
116118

@@ -133,6 +135,13 @@ func processPayload[P Payload](m *BoostService, log *logrus.Entry, ua UserAgent,
133135
func verifyPayload[P Payload](payload P, log *logrus.Entry, response *builderApi.VersionedSubmitBlindedBlockResponse) error {
134136
// Step 1: verify version
135137
switch any(payload).(type) {
138+
case *eth2ApiV1Capella.SignedBlindedBeaconBlock:
139+
if response.Version != spec.DataVersionCapella {
140+
log.WithFields(logrus.Fields{
141+
"version": response.Version,
142+
}).Error("response version was not capella")
143+
return errInvalidVersion
144+
}
136145
case *eth2ApiV1Deneb.SignedBlindedBeaconBlock:
137146
if response.Version != spec.DataVersionDeneb {
138147
log.WithFields(logrus.Fields{
@@ -155,38 +164,41 @@ func verifyPayload[P Payload](payload P, log *logrus.Entry, response *builderApi
155164
return errEmptyPayload
156165
}
157166

158-
// TODO(MariusVanDerWijden): make this generic once
159-
// execution payload or blobs bundle change between forks.
160-
var (
161-
executionPayload *deneb.ExecutionPayload
162-
blobs *denebApi.BlobsBundle
163-
)
164-
165-
switch any(payload).(type) {
167+
// Step 3: verify post-conditions
168+
switch block := any(payload).(type) {
169+
case *eth2ApiV1Capella.SignedBlindedBeaconBlock:
170+
if err := verifyBlockhash(log, payload, response.Capella.BlockHash); err != nil {
171+
return err
172+
}
166173
case *eth2ApiV1Deneb.SignedBlindedBeaconBlock:
167-
executionPayload = response.Deneb.ExecutionPayload
168-
blobs = response.Deneb.BlobsBundle
174+
if err := verifyBlockhash(log, payload, response.Deneb.ExecutionPayload.BlockHash); err != nil {
175+
return err
176+
}
177+
if err := verifyKZGCommitments(log, response.Deneb.BlobsBundle, block.Message.Body.BlobKZGCommitments); err != nil {
178+
return err
179+
}
169180
case *eth2ApiV1Electra.SignedBlindedBeaconBlock:
170-
executionPayload = response.Electra.ExecutionPayload
171-
blobs = response.Electra.BlobsBundle
181+
if err := verifyBlockhash(log, payload, response.Electra.ExecutionPayload.BlockHash); err != nil {
182+
return err
183+
}
184+
if err := verifyKZGCommitments(log, response.Electra.BlobsBundle, block.Message.Body.BlobKZGCommitments); err != nil {
185+
return err
186+
}
172187
}
188+
return nil
189+
}
173190

174-
// Step 3: Ensure the response blockhash matches the request
175-
if blockHash[P](payload) != executionPayload.BlockHash {
191+
func verifyBlockhash[P Payload](log *logrus.Entry, payload P, executionPayloadHash phase0.Hash32) error {
192+
if blockHash(payload) != executionPayloadHash {
176193
log.WithFields(logrus.Fields{
177-
"responseBlockHash": executionPayload.String(),
194+
"responseBlockHash": executionPayloadHash.String(),
178195
}).Error("requestBlockHash does not equal responseBlockHash")
179196
return errInvalidBlockhash
180197
}
198+
return nil
199+
}
181200

182-
// Step 4: Verify KZG commitments
183-
var commitments []deneb.KZGCommitment
184-
switch block := any(payload).(type) {
185-
case *eth2ApiV1Deneb.SignedBlindedBeaconBlock:
186-
commitments = block.Message.Body.BlobKZGCommitments
187-
case *eth2ApiV1Electra.SignedBlindedBeaconBlock:
188-
commitments = block.Message.Body.BlobKZGCommitments
189-
}
201+
func verifyKZGCommitments(log *logrus.Entry, blobs *denebApi.BlobsBundle, commitments []deneb.KZGCommitment) error {
190202
// Ensure that blobs are valid and matches the request
191203
if len(commitments) != len(blobs.Blobs) || len(commitments) != len(blobs.Commitments) || len(commitments) != len(blobs.Proofs) {
192204
log.WithFields(logrus.Fields{
@@ -213,6 +225,14 @@ func verifyPayload[P Payload](payload P, log *logrus.Entry, response *builderApi
213225

214226
func prepareLogger[P Payload](log *logrus.Entry, payload P, userAgent UserAgent, slotUID string) *logrus.Entry {
215227
switch block := any(payload).(type) {
228+
case *eth2ApiV1Capella.SignedBlindedBeaconBlock:
229+
return log.WithFields(logrus.Fields{
230+
"ua": userAgent,
231+
"slot": block.Message.Slot,
232+
"blockHash": block.Message.Body.ExecutionPayloadHeader.BlockHash.String(),
233+
"parentHash": block.Message.Body.ExecutionPayloadHeader.ParentHash.String(),
234+
"slotUID": slotUID,
235+
})
216236
case *eth2ApiV1Deneb.SignedBlindedBeaconBlock:
217237
return log.WithFields(logrus.Fields{
218238
"ua": userAgent,
@@ -235,6 +255,8 @@ func prepareLogger[P Payload](log *logrus.Entry, payload P, userAgent UserAgent,
235255

236256
func slot[P Payload](payload P) uint64 {
237257
switch block := any(payload).(type) {
258+
case *eth2ApiV1Capella.SignedBlindedBeaconBlock:
259+
return uint64(block.Message.Slot)
238260
case *eth2ApiV1Deneb.SignedBlindedBeaconBlock:
239261
return uint64(block.Message.Slot)
240262
case *eth2ApiV1Electra.SignedBlindedBeaconBlock:
@@ -245,6 +267,8 @@ func slot[P Payload](payload P) uint64 {
245267

246268
func blockHash[P Payload](payload P) phase0.Hash32 {
247269
switch block := any(payload).(type) {
270+
case *eth2ApiV1Capella.SignedBlindedBeaconBlock:
271+
return block.Message.Body.ExecutionPayloadHeader.BlockHash
248272
case *eth2ApiV1Deneb.SignedBlindedBeaconBlock:
249273
return block.Message.Body.ExecutionPayloadHeader.BlockHash
250274
case *eth2ApiV1Electra.SignedBlindedBeaconBlock:

server/mock/mock_relay.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
builderApi "github.com/attestantio/go-builder-client/api"
14+
builderApiCapella "github.com/attestantio/go-builder-client/api/capella"
1415
builderApiDeneb "github.com/attestantio/go-builder-client/api/deneb"
1516
builderApiElectra "github.com/attestantio/go-builder-client/api/electra"
1617
builderApiV1 "github.com/attestantio/go-builder-client/api/v1"
@@ -173,6 +174,27 @@ func (m *Relay) defaultHandleRegisterValidator(w http.ResponseWriter, req *http.
173174
// method
174175
func (m *Relay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, publicKey string, version spec.DataVersion) *builderSpec.VersionedSignedBuilderBid {
175176
switch version {
177+
case spec.DataVersionCapella:
178+
// Fill the payload with custom values.
179+
message := &builderApiCapella.BuilderBid{
180+
Header: &capella.ExecutionPayloadHeader{
181+
BlockHash: HexToHash(blockHash),
182+
ParentHash: HexToHash(parentHash),
183+
WithdrawalsRoot: phase0.Root{},
184+
},
185+
Value: uint256.NewInt(value),
186+
Pubkey: HexToPubkey(publicKey),
187+
}
188+
// Sign the message.
189+
signature, err := ssz.SignMessage(message, ssz.DomainBuilder, m.secretKey)
190+
require.NoError(m.t, err)
191+
return &builderSpec.VersionedSignedBuilderBid{
192+
Version: spec.DataVersionCapella,
193+
Capella: &builderApiCapella.SignedBuilderBid{
194+
Message: message,
195+
Signature: signature,
196+
},
197+
}
176198
case spec.DataVersionDeneb:
177199
message := &builderApiDeneb.BuilderBid{
178200
Header: &deneb.ExecutionPayloadHeader{
@@ -222,7 +244,7 @@ func (m *Relay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, publi
222244
Signature: signature,
223245
},
224246
}
225-
case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix, spec.DataVersionCapella:
247+
case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix:
226248
return nil
227249
}
228250
return nil

server/service.go

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
builderApi "github.com/attestantio/go-builder-client/api"
1919
builderApiV1 "github.com/attestantio/go-builder-client/api/v1"
20+
eth2ApiV1Capella "github.com/attestantio/go-eth2-client/api/v1/capella"
2021
eth2ApiV1Deneb "github.com/attestantio/go-eth2-client/api/v1/deneb"
2122
eth2ApiV1Electra "github.com/attestantio/go-eth2-client/api/v1/electra"
2223
"github.com/attestantio/go-eth2-client/spec/phase0"
@@ -369,25 +370,54 @@ func (m *BoostService) handleGetPayload(w http.ResponseWriter, req *http.Request
369370
// Read user agent for logging
370371
userAgent := UserAgent(req.Header.Get("User-Agent"))
371372

373+
// New forks need to be added at the front of this array.
374+
// The ordering of the array conveys precedence of the decoders.
375+
//nolint: forcetypeassert
376+
decoders := []struct {
377+
fork string
378+
payload any
379+
processor func(payload any) (*builderApi.VersionedSubmitBlindedBlockResponse, bidResp)
380+
}{
381+
{
382+
fork: "Electra",
383+
payload: new(eth2ApiV1Electra.SignedBlindedBeaconBlock),
384+
processor: func(payload any) (*builderApi.VersionedSubmitBlindedBlockResponse, bidResp) {
385+
return processPayload[*eth2ApiV1Electra.SignedBlindedBeaconBlock](m, log, userAgent, payload.(*eth2ApiV1Electra.SignedBlindedBeaconBlock))
386+
},
387+
},
388+
{
389+
fork: "Deneb",
390+
payload: new(eth2ApiV1Deneb.SignedBlindedBeaconBlock),
391+
processor: func(payload any) (*builderApi.VersionedSubmitBlindedBlockResponse, bidResp) {
392+
return processPayload[*eth2ApiV1Deneb.SignedBlindedBeaconBlock](m, log, userAgent, payload.(*eth2ApiV1Deneb.SignedBlindedBeaconBlock))
393+
},
394+
},
395+
{
396+
fork: "Capella",
397+
payload: new(eth2ApiV1Capella.SignedBlindedBeaconBlock),
398+
processor: func(payload any) (*builderApi.VersionedSubmitBlindedBlockResponse, bidResp) {
399+
return processPayload[*eth2ApiV1Capella.SignedBlindedBeaconBlock](m, log, userAgent, payload.(*eth2ApiV1Capella.SignedBlindedBeaconBlock))
400+
},
401+
},
402+
}
403+
372404
// Decode the body now
373-
payload := new(eth2ApiV1Electra.SignedBlindedBeaconBlock)
374-
log.Debug("attempting to decode body into Electra payload")
375-
if err := DecodeJSON(bytes.NewReader(body), payload); err != nil {
376-
log.Debug("could not decode Electra request payload, attempting to decode body into Deneb payload")
377-
payload := new(eth2ApiV1Deneb.SignedBlindedBeaconBlock)
405+
for _, decoder := range decoders {
406+
payload := decoder.payload
407+
// decode
408+
log.Debugf("attempting to decode body into %v payload", decoder.fork)
378409
if err := DecodeJSON(bytes.NewReader(body), payload); err != nil {
379-
log.Debug("could not decode Deneb request payload")
380-
log.WithError(err).WithField("body", string(body)).Error("could not decode request payload from the beacon-node (signed blinded beacon block)")
381-
m.respondError(w, http.StatusBadRequest, err.Error())
382-
return
410+
log.Debugf("could not decode %v request payload", decoder.fork)
411+
continue
383412
}
384-
385-
result, originalBid := processPayload[*eth2ApiV1Deneb.SignedBlindedBeaconBlock](m, log, userAgent, payload)
413+
// process
414+
result, originalBid := decoder.processor(payload)
386415
m.respondPayload(w, log, result, originalBid)
387416
return
388417
}
389-
result, originalBid := processPayload[*eth2ApiV1Electra.SignedBlindedBeaconBlock](m, log, userAgent, payload)
390-
m.respondPayload(w, log, result, originalBid)
418+
// no decoder was able to decode the body, log error
419+
log.WithError(err).WithField("body", string(body)).Error("could not decode request payload from the beacon-node (signed blinded beacon block)")
420+
m.respondError(w, http.StatusBadRequest, "could not decode body")
391421
}
392422

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

server/service_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
builderApiDeneb "github.com/attestantio/go-builder-client/api/deneb"
1919
builderApiV1 "github.com/attestantio/go-builder-client/api/v1"
2020
builderSpec "github.com/attestantio/go-builder-client/spec"
21+
eth2ApiV1Capella "github.com/attestantio/go-eth2-client/api/v1/capella"
2122
eth2ApiV1Deneb "github.com/attestantio/go-eth2-client/api/v1/deneb"
2223
eth2ApiV1Electra "github.com/attestantio/go-eth2-client/api/v1/electra"
2324
"github.com/attestantio/go-eth2-client/spec"
@@ -91,6 +92,27 @@ func (be *testBackend) request(t *testing.T, method, path string, payload any) *
9192
return rr
9293
}
9394

95+
func blindedBlockToExecutionPayloadCapella(signedBlindedBeaconBlock *eth2ApiV1Capella.SignedBlindedBeaconBlock) *capella.ExecutionPayload {
96+
header := signedBlindedBeaconBlock.Message.Body.ExecutionPayloadHeader
97+
return &capella.ExecutionPayload{
98+
ParentHash: header.ParentHash,
99+
FeeRecipient: header.FeeRecipient,
100+
StateRoot: header.StateRoot,
101+
ReceiptsRoot: header.ReceiptsRoot,
102+
LogsBloom: header.LogsBloom,
103+
PrevRandao: header.PrevRandao,
104+
BlockNumber: header.BlockNumber,
105+
GasLimit: header.GasLimit,
106+
GasUsed: header.GasUsed,
107+
Timestamp: header.Timestamp,
108+
ExtraData: header.ExtraData,
109+
BaseFeePerGas: header.BaseFeePerGas,
110+
BlockHash: header.BlockHash,
111+
Transactions: make([]bellatrix.Transaction, 0),
112+
Withdrawals: make([]*capella.Withdrawal, 0),
113+
}
114+
}
115+
94116
func blindedBlockContentsToPayloadDeneb(signedBlindedBlockContents *eth2ApiV1Deneb.SignedBlindedBeaconBlock) *builderApiDeneb.ExecutionPayloadAndBlobsBundle {
95117
header := signedBlindedBlockContents.Message.Body.ExecutionPayloadHeader
96118
numBlobs := len(signedBlindedBlockContents.Message.Body.BlobKZGCommitments)
@@ -795,6 +817,62 @@ func TestEmptyTxRoot(t *testing.T) {
795817
require.Equal(t, "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1", txRootHex)
796818
}
797819

820+
func TestGetPayloadWithTestdata(t *testing.T) {
821+
path := "/eth/v1/builder/blinded_blocks"
822+
testPayloadsFiles := []string{
823+
"../testdata/signed-blinded-beacon-block-capella.json",
824+
}
825+
for _, fn := range testPayloadsFiles {
826+
t.Run(fn, func(t *testing.T) {
827+
jsonFile, err := os.Open(fn)
828+
require.NoError(t, err)
829+
defer jsonFile.Close()
830+
signedBlindedBeaconBlock := new(eth2ApiV1Capella.SignedBlindedBeaconBlock)
831+
require.NoError(t, DecodeJSON(jsonFile, &signedBlindedBeaconBlock))
832+
backend := newTestBackend(t, 1, time.Second)
833+
mockResp := builderApi.VersionedSubmitBlindedBlockResponse{
834+
Version: spec.DataVersionCapella,
835+
Capella: &capella.ExecutionPayload{
836+
BlockHash: signedBlindedBeaconBlock.Message.Body.ExecutionPayloadHeader.BlockHash,
837+
Withdrawals: make([]*capella.Withdrawal, 0),
838+
},
839+
}
840+
backend.relays[0].GetPayloadResponse = &mockResp
841+
rr := backend.request(t, http.MethodPost, path, signedBlindedBeaconBlock)
842+
require.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
843+
require.Equal(t, 1, backend.relays[0].GetRequestCount(path))
844+
resp := new(builderApi.VersionedSubmitBlindedBlockResponse)
845+
err = json.Unmarshal(rr.Body.Bytes(), resp)
846+
require.NoError(t, err)
847+
require.Equal(t, signedBlindedBeaconBlock.Message.Body.ExecutionPayloadHeader.BlockHash, resp.Capella.BlockHash)
848+
})
849+
}
850+
}
851+
852+
func TestGetPayloadCapella(t *testing.T) {
853+
// Load the signed blinded beacon block used for getPayload
854+
jsonFile, err := os.Open("../testdata/signed-blinded-beacon-block-capella.json")
855+
require.NoError(t, err)
856+
defer jsonFile.Close()
857+
signedBlindedBeaconBlock := new(eth2ApiV1Capella.SignedBlindedBeaconBlock)
858+
require.NoError(t, DecodeJSON(jsonFile, &signedBlindedBeaconBlock))
859+
backend := newTestBackend(t, 1, time.Second)
860+
// Prepare getPayload response
861+
backend.relays[0].GetPayloadResponse = &builderApi.VersionedSubmitBlindedBlockResponse{
862+
Version: spec.DataVersionCapella,
863+
Capella: blindedBlockToExecutionPayloadCapella(signedBlindedBeaconBlock),
864+
}
865+
// call getPayload, ensure it's only called on relay 0 (origin of the bid)
866+
getPayloadPath := "/eth/v1/builder/blinded_blocks"
867+
rr := backend.request(t, http.MethodPost, getPayloadPath, signedBlindedBeaconBlock)
868+
require.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
869+
require.Equal(t, 1, backend.relays[0].GetRequestCount(getPayloadPath))
870+
resp := new(builderApi.VersionedSubmitBlindedBlockResponse)
871+
err = json.Unmarshal(rr.Body.Bytes(), resp)
872+
require.NoError(t, err)
873+
require.Equal(t, signedBlindedBeaconBlock.Message.Body.ExecutionPayloadHeader.BlockHash, resp.Capella.BlockHash)
874+
}
875+
798876
func TestGetPayloadDeneb(t *testing.T) {
799877
// Load the signed blinded beacon block used for getPayload
800878
jsonFile, err := os.Open("../testdata/signed-blinded-beacon-block-deneb.json")

server/utils.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ func checkRelaySignature(bid *builderSpec.VersionedSignedBuilderBid, domain phas
242242

243243
func getPayloadResponseIsEmpty(payload *builderApi.VersionedSubmitBlindedBlockResponse) bool {
244244
switch payload.Version {
245+
case spec.DataVersionCapella:
246+
if payload.Capella == nil || payload.Capella.BlockHash == nilHash {
247+
return true
248+
}
245249
case spec.DataVersionDeneb:
246250
if payload.Deneb == nil || payload.Deneb.ExecutionPayload == nil ||
247251
payload.Deneb.ExecutionPayload.BlockHash == nilHash ||
@@ -254,7 +258,7 @@ func getPayloadResponseIsEmpty(payload *builderApi.VersionedSubmitBlindedBlockRe
254258
payload.Electra.BlobsBundle == nil {
255259
return true
256260
}
257-
case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix, spec.DataVersionCapella:
261+
case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix:
258262
return true
259263
}
260264
return false

0 commit comments

Comments
 (0)