From 9726e44ed6bd46b7e69df57045ee80dbc2d1c515 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Tue, 22 Apr 2025 10:12:16 -0500 Subject: [PATCH 01/10] Initial fulu support --- go.mod | 4 + go.sum | 8 +- server/constants.go | 1 + server/get_header.go | 7 ++ server/get_payload.go | 222 +++++++++++++++++++++++--------------- server/mock/mock_relay.go | 25 +++++ server/service_test.go | 24 ++++- server/utils.go | 6 ++ 8 files changed, 204 insertions(+), 93 deletions(-) diff --git a/go.mod b/go.mod index 93beee7f..2987e4d4 100644 --- a/go.mod +++ b/go.mod @@ -60,3 +60,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/attestantio/go-eth2-client => github.com/jtraglia/go-eth2-client v0.21.5-0.20250410190628-e845cec4674f + +replace github.com/attestantio/go-builder-client => github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546 diff --git a/go.sum b/go.sum index f2ceecbe..758274a7 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= -github.com/attestantio/go-builder-client v0.6.1 h1:fn6PC8aDWx2YbptstR1JKP8NyakiNJJTiOE5f9N0z5Q= -github.com/attestantio/go-builder-client v0.6.1/go.mod h1:f8wi3HzuPxfJoi2PirpJK3yZhte4SavDgKJbRrKoB1Q= -github.com/attestantio/go-eth2-client v0.25.0 h1:wLQxoteGCbTE/vKCMASx1ze+Zm9rcqtltRnblaLJup4= -github.com/attestantio/go-eth2-client v0.25.0/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -59,6 +55,10 @@ github.com/huandu/go-clone v1.7.2 h1:3+Aq0Ed8XK+zKkLjE2dfHg0XrpIfcohBE1K+c8Usxoo github.com/huandu/go-clone v1.7.2/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0XLdbUYN5U= github.com/huandu/go-clone/generic v1.6.0/go.mod h1:xgd9ZebcMsBWWcBx5mVMCoqMX24gLWr5lQicr+nVXNs= +github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546 h1:UnMUnbUz6fiJ1Ox7hNMkAS6T+Vcdy2qjo9q2D3q8zqg= +github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546/go.mod h1:3Z2KfjbuX2EmJajEmDb3I/JdDqzlm4WsojnCugO0oM0= +github.com/jtraglia/go-eth2-client v0.21.5-0.20250410190628-e845cec4674f h1:XacizBmPNKeJK/MdsbEUC/U6LVUlsuPsvgC8ia8QPBI= +github.com/jtraglia/go-eth2-client v0.21.5-0.20250410190628-e845cec4674f/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/server/constants.go b/server/constants.go index ff7d9e24..c4273a8c 100644 --- a/server/constants.go +++ b/server/constants.go @@ -17,4 +17,5 @@ const ( EthConsensusVersionCapella = "capella" EthConsensusVersionDeneb = "deneb" EthConsensusVersionElectra = "electra" + EthConsensusVersionFulu = "fulu" ) diff --git a/server/get_header.go b/server/get_header.go index 0149f2fa..942acf9b 100644 --- a/server/get_header.go +++ b/server/get_header.go @@ -279,6 +279,10 @@ func decodeBid(respBytes []byte, respContentType, ethConsensusVersion string, bi bid.Version = spec.DataVersionElectra bid.Electra = new(builderApiElectra.SignedBuilderBid) return bid.Electra.UnmarshalSSZ(respBytes) + case EthConsensusVersionFulu: + bid.Version = spec.DataVersionFulu + bid.Fulu = new(builderApiElectra.SignedBuilderBid) + return bid.Fulu.UnmarshalSSZ(respBytes) default: return errInvalidForkVersion } @@ -322,6 +326,9 @@ func (m *BoostService) respondGetHeaderSSZ(w http.ResponseWriter, result *bidRes case spec.DataVersionElectra: w.Header().Set(HeaderEthConsensusVersion, EthConsensusVersionElectra) sszData, err = result.response.Electra.MarshalSSZ() + case spec.DataVersionFulu: + w.Header().Set(HeaderEthConsensusVersion, EthConsensusVersionFulu) + sszData, err = result.response.Fulu.MarshalSSZ() case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair: err = errInvalidForkVersion } diff --git a/server/get_payload.go b/server/get_payload.go index 29dbcaeb..7ca6fb35 100644 --- a/server/get_payload.go +++ b/server/get_payload.go @@ -14,6 +14,7 @@ import ( builderApi "github.com/attestantio/go-builder-client/api" builderApiDeneb "github.com/attestantio/go-builder-client/api/deneb" + builderApiFulu "github.com/attestantio/go-builder-client/api/fulu" eth2Api "github.com/attestantio/go-eth2-client/api" eth2ApiV1Bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" eth2ApiV1Capella "github.com/attestantio/go-eth2-client/api/v1/capella" @@ -35,7 +36,6 @@ var ( errInvalidBlockhash = errors.New("invalid blockhash") errInvalidKZGLength = errors.New("invalid KZG commitments length") errInvalidKZG = errors.New("invalid KZG commitment") - errFailedToDecode = errors.New("failed to decode payload") errFailedToConvert = errors.New("failed to convert block from SSZ to JSON") ) @@ -337,31 +337,58 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde return err } - // Ensure the blobs bundle field counts are correct - if len(requestCommitments) != len(responseBlobsBundle.Blobs) || - len(requestCommitments) != len(responseBlobsBundle.Commitments) || - len(requestCommitments) != len(responseBlobsBundle.Proofs) { + // Check commitments + responseCommitments, err := responseBlobsBundle.Commitments() + if err != nil { + log.WithError(err).Error("failed to get response commitments") + return err + } + if len(requestCommitments) != len(responseCommitments) { log.WithFields(logrus.Fields{ - "requestBlobCommitments": len(requestCommitments), - "responseBlobs": len(responseBlobsBundle.Blobs), - "responseBlobCommitments": len(responseBlobsBundle.Commitments), - "responseBlobProofs": len(responseBlobsBundle.Proofs), - }).Error("different lengths for blobs/commitments/proofs") + "requestBlobCommitments": len(requestCommitments), + "responseCommitments": len(responseCommitments), + }).Error("different lengths for commitments") return errInvalidKZGLength } - - // Ensure the request and response KZG commitments are the same for i, commitment := range requestCommitments { - if commitment != responseBlobsBundle.Commitments[i] { + if commitment != responseCommitments[i] { log.WithFields(logrus.Fields{ "index": i, "requestBlobCommitment": commitment.String(), - "responseBlobCommitment": responseBlobsBundle.Commitments[i].String(), + "responseBlobCommitment": responseCommitments[i].String(), }).Error("requestBlobCommitment does not equal responseBlobCommitment") return errInvalidKZG } } + // Check proofs + responseProofs, err := responseBlobsBundle.Proofs() + if err != nil { + log.WithError(err).Error("failed to get response blobs") + return err + } + if len(requestCommitments) != len(responseProofs) { + log.WithFields(logrus.Fields{ + "requestBlobCommitments": len(requestCommitments), + "responseProofss": len(responseProofs), + }).Error("different lengths for proofs") + return errInvalidKZGLength + } + + // Check blobs + responseBlobs, err := responseBlobsBundle.Blobs() + if err != nil { + log.WithError(err).Error("failed to get response blobs") + return err + } + if len(requestCommitments) != len(responseBlobs) { + log.WithFields(logrus.Fields{ + "requestBlobCommitments": len(requestCommitments), + "responseBlobs": len(responseBlobs), + }).Error("different lengths for blobs") + return errInvalidKZGLength + } + return nil } @@ -386,6 +413,8 @@ func convertSSZToJSON(ethConsensusVersion string, sszBytes []byte) ([]byte, erro block = new(eth2ApiV1Deneb.SignedBlindedBeaconBlock) case EthConsensusVersionElectra: block = new(eth2ApiV1Electra.SignedBlindedBeaconBlock) + case EthConsensusVersionFulu: + block = new(eth2ApiV1Electra.SignedBlindedBeaconBlock) default: return nil, errInvalidForkVersion } @@ -404,61 +433,78 @@ func convertSSZToJSON(ethConsensusVersion string, sszBytes []byte) ([]byte, erro func decodeSignedBlindedBeaconBlock(in []byte, contentType, ethConsensusVersion string, out *eth2Api.VersionedSignedBlindedBeaconBlock) error { switch contentType { case MediaTypeOctetStream: - if ethConsensusVersion != "" { - switch ethConsensusVersion { - case EthConsensusVersionBellatrix: - out.Version = spec.DataVersionBellatrix - out.Bellatrix = new(eth2ApiV1Bellatrix.SignedBlindedBeaconBlock) - return out.Bellatrix.UnmarshalSSZ(in) - case EthConsensusVersionCapella: - out.Version = spec.DataVersionCapella - out.Capella = new(eth2ApiV1Capella.SignedBlindedBeaconBlock) - return out.Capella.UnmarshalSSZ(in) - case EthConsensusVersionDeneb: - out.Version = spec.DataVersionDeneb - out.Deneb = new(eth2ApiV1Deneb.SignedBlindedBeaconBlock) - return out.Deneb.UnmarshalSSZ(in) - case EthConsensusVersionElectra: - out.Version = spec.DataVersionElectra - out.Electra = new(eth2ApiV1Electra.SignedBlindedBeaconBlock) - return out.Electra.UnmarshalSSZ(in) - default: - return errInvalidForkVersion - } - } else { + if ethConsensusVersion == "" { return types.ErrMissingEthConsensusVersion } - case MediaTypeJSON: - var err error - electraBlock := new(eth2ApiV1Electra.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, electraBlock) - if err == nil { - out.Version = spec.DataVersionElectra - out.Electra = electraBlock - return nil - } - denebBlock := new(eth2ApiV1Deneb.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, denebBlock) - if err == nil { + switch ethConsensusVersion { + case EthConsensusVersionBellatrix: + out.Version = spec.DataVersionBellatrix + out.Bellatrix = new(eth2ApiV1Bellatrix.SignedBlindedBeaconBlock) + return out.Bellatrix.UnmarshalSSZ(in) + case EthConsensusVersionCapella: + out.Version = spec.DataVersionCapella + out.Capella = new(eth2ApiV1Capella.SignedBlindedBeaconBlock) + return out.Capella.UnmarshalSSZ(in) + case EthConsensusVersionDeneb: out.Version = spec.DataVersionDeneb - out.Deneb = denebBlock - return nil + out.Deneb = new(eth2ApiV1Deneb.SignedBlindedBeaconBlock) + return out.Deneb.UnmarshalSSZ(in) + case EthConsensusVersionElectra: + out.Version = spec.DataVersionElectra + out.Electra = new(eth2ApiV1Electra.SignedBlindedBeaconBlock) + return out.Electra.UnmarshalSSZ(in) + case EthConsensusVersionFulu: + out.Version = spec.DataVersionFulu + out.Fulu = new(eth2ApiV1Electra.SignedBlindedBeaconBlock) + return out.Fulu.UnmarshalSSZ(in) + default: + return errInvalidForkVersion } - capellaBlock := new(eth2ApiV1Capella.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, capellaBlock) - if err == nil { - out.Version = spec.DataVersionCapella - out.Capella = capellaBlock - return nil + case MediaTypeJSON: + if ethConsensusVersion == "" { + return types.ErrMissingEthConsensusVersion } - bellatrixBlock := new(eth2ApiV1Bellatrix.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, bellatrixBlock) - if err == nil { - out.Version = spec.DataVersionBellatrix - out.Bellatrix = bellatrixBlock - return nil + var err error + switch ethConsensusVersion { + case EthConsensusVersionBellatrix: + bellatrixBlock := new(eth2ApiV1Bellatrix.SignedBlindedBeaconBlock) + err = json.Unmarshal(in, bellatrixBlock) + if err == nil { + out.Version = spec.DataVersionBellatrix + out.Bellatrix = bellatrixBlock + } + case EthConsensusVersionCapella: + capellaBlock := new(eth2ApiV1Capella.SignedBlindedBeaconBlock) + err = json.Unmarshal(in, capellaBlock) + if err == nil { + out.Version = spec.DataVersionCapella + out.Capella = capellaBlock + } + case EthConsensusVersionDeneb: + denebBlock := new(eth2ApiV1Deneb.SignedBlindedBeaconBlock) + err = json.Unmarshal(in, denebBlock) + if err == nil { + out.Version = spec.DataVersionDeneb + out.Deneb = denebBlock + } + case EthConsensusVersionElectra: + electraBlock := new(eth2ApiV1Electra.SignedBlindedBeaconBlock) + err = json.Unmarshal(in, electraBlock) + if err == nil { + out.Version = spec.DataVersionElectra + out.Electra = electraBlock + } + case EthConsensusVersionFulu: + fuluBlock := new(eth2ApiV1Electra.SignedBlindedBeaconBlock) + err = json.Unmarshal(in, fuluBlock) + if err == nil { + out.Version = spec.DataVersionFulu + out.Fulu = fuluBlock + } + default: + return errInvalidForkVersion } - return errFailedToDecode + return err } return types.ErrInvalidContentType } @@ -467,30 +513,33 @@ func decodeSignedBlindedBeaconBlock(in []byte, contentType, ethConsensusVersion func decodeSubmitBlindedBlockResponse(in []byte, contentType, ethConsensusVersion string, out *builderApi.VersionedSubmitBlindedBlockResponse) error { switch contentType { case MediaTypeOctetStream: - if ethConsensusVersion != "" { - switch ethConsensusVersion { - case EthConsensusVersionBellatrix: - out.Version = spec.DataVersionBellatrix - out.Bellatrix = new(bellatrix.ExecutionPayload) - return out.Bellatrix.UnmarshalSSZ(in) - case EthConsensusVersionCapella: - out.Version = spec.DataVersionCapella - out.Capella = new(capella.ExecutionPayload) - return out.Capella.UnmarshalSSZ(in) - case EthConsensusVersionDeneb: - out.Version = spec.DataVersionDeneb - out.Deneb = new(builderApiDeneb.ExecutionPayloadAndBlobsBundle) - return out.Deneb.UnmarshalSSZ(in) - case EthConsensusVersionElectra: - out.Version = spec.DataVersionElectra - out.Electra = new(builderApiDeneb.ExecutionPayloadAndBlobsBundle) - return out.Electra.UnmarshalSSZ(in) - default: - return errInvalidForkVersion - } - } else { + if ethConsensusVersion == "" { return types.ErrMissingEthConsensusVersion } + switch ethConsensusVersion { + case EthConsensusVersionBellatrix: + out.Version = spec.DataVersionBellatrix + out.Bellatrix = new(bellatrix.ExecutionPayload) + return out.Bellatrix.UnmarshalSSZ(in) + case EthConsensusVersionCapella: + out.Version = spec.DataVersionCapella + out.Capella = new(capella.ExecutionPayload) + return out.Capella.UnmarshalSSZ(in) + case EthConsensusVersionDeneb: + out.Version = spec.DataVersionDeneb + out.Deneb = new(builderApiDeneb.ExecutionPayloadAndBlobsBundle) + return out.Deneb.UnmarshalSSZ(in) + case EthConsensusVersionElectra: + out.Version = spec.DataVersionElectra + out.Electra = new(builderApiDeneb.ExecutionPayloadAndBlobsBundle) + return out.Electra.UnmarshalSSZ(in) + case EthConsensusVersionFulu: + out.Version = spec.DataVersionFulu + out.Fulu = new(builderApiFulu.ExecutionPayloadAndBlobsBundle) + return out.Fulu.UnmarshalSSZ(in) + default: + return errInvalidForkVersion + } case MediaTypeJSON: return json.Unmarshal(in, out) } @@ -531,6 +580,9 @@ func (m *BoostService) respondGetPayloadSSZ(w http.ResponseWriter, result *build case spec.DataVersionElectra: w.Header().Set(HeaderEthConsensusVersion, EthConsensusVersionElectra) sszData, err = result.Electra.MarshalSSZ() + case spec.DataVersionFulu: + w.Header().Set(HeaderEthConsensusVersion, EthConsensusVersionFulu) + sszData, err = result.Fulu.MarshalSSZ() case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair: err = errInvalidForkVersion } diff --git a/server/mock/mock_relay.go b/server/mock/mock_relay.go index f89cea87..2f6fcbcd 100644 --- a/server/mock/mock_relay.go +++ b/server/mock/mock_relay.go @@ -272,6 +272,31 @@ func (m *Relay) MakeGetHeaderResponse(value uint64, blockHash, parentHash, publi Signature: signature, }, } + case spec.DataVersionFulu: + message := &builderApiElectra.BuilderBid{ + Header: &deneb.ExecutionPayloadHeader{ + BlockHash: HexToHash(blockHash), + ParentHash: HexToHash(parentHash), + WithdrawalsRoot: phase0.Root{}, + BaseFeePerGas: uint256.NewInt(0), + }, + BlobKZGCommitments: make([]deneb.KZGCommitment, 0), + ExecutionRequests: &electra.ExecutionRequests{}, + Value: uint256.NewInt(value), + Pubkey: HexToPubkey(publicKey), + } + + // Sign the message. + signature, err := ssz.SignMessage(message, ssz.DomainBuilder, m.secretKey) + require.NoError(m.t, err) + + return &builderSpec.VersionedSignedBuilderBid{ + Version: spec.DataVersionFulu, + Fulu: &builderApiElectra.SignedBuilderBid{ + Message: message, + Signature: signature, + }, + } case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix: return nil } diff --git a/server/service_test.go b/server/service_test.go index cf756d14..804e04f4 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "path/filepath" + "regexp" "strings" "testing" "time" @@ -796,6 +797,7 @@ func TestGetPayload(t *testing.T) { t.Run("Okay response from relay in JSON", func(t *testing.T) { header := make(http.Header) header.Set(HeaderAccept, MediaTypeJSON) + header.Set("Eth-Consensus-Version", "deneb") backend := newTestBackend(t, 1, time.Second) @@ -976,6 +978,7 @@ func TestGetPayload(t *testing.T) { t.Run("Bad response from relays", func(t *testing.T) { header := make(http.Header) header.Set(HeaderAccept, MediaTypeJSON) + header.Set("Eth-Consensus-Version", "deneb") backend := newTestBackend(t, 2, time.Second) @@ -1024,6 +1027,7 @@ func TestGetPayload(t *testing.T) { t.Run("Retries on error from relay", func(t *testing.T) { header := make(http.Header) header.Set(HeaderAccept, MediaTypeJSON) + header.Set("Eth-Consensus-Version", "deneb") backend := newTestBackend(t, 1, 2*time.Second) @@ -1053,6 +1057,7 @@ func TestGetPayload(t *testing.T) { t.Run("Error after max retries are reached", func(t *testing.T) { header := make(http.Header) header.Set(HeaderAccept, MediaTypeJSON) + header.Set("Eth-Consensus-Version", "deneb") backend := newTestBackend(t, 1, time.Second) @@ -1130,6 +1135,7 @@ func TestEmptyTxRoot(t *testing.T) { } func blindedBlockToBlockResponse(signedBlock any) *builderApi.VersionedSubmitBlindedBlockResponse { + // TODO(jtraglia): How do we update this to work with Fulu too? switch block := signedBlock.(type) { case *eth2ApiV1Bellatrix.SignedBlindedBeaconBlock: header := block.Message.Body.ExecutionPayloadHeader @@ -1230,9 +1236,6 @@ func denebExecutionPayloadAndBlobsBundle(header *deneb.ExecutionPayloadHeader, k func TestGetPayloadForks(t *testing.T) { t.Parallel() - header := http.Header{} - header.Set("Accept", "application/json") - // Get a list of testdata files pattern := "../testdata/signed-blinded-beacon-block-*.json" files, err := filepath.Glob(pattern) @@ -1248,12 +1251,22 @@ func TestGetPayloadForks(t *testing.T) { jsonBytes, err := os.ReadFile(file) require.NoError(t, err) + re := regexp.MustCompile(`^.*signed-blinded-beacon-block-(.+)\.json$`) + matches := re.FindStringSubmatch(file) + require.Len(t, matches, 2, "filename did not match expected pattern") + consensusVersion := matches[1] + + header := http.Header{} + header.Set("Accept", "application/json") + header.Set("Content-Type", "application/json") + header.Set("Eth-Consensus-Version", consensusVersion) + // Create a new backend backend := newTestBackend(t, 1, time.Second) // Decode the block block := new(eth2Api.VersionedSignedBlindedBeaconBlock) - err = decodeSignedBlindedBeaconBlock(jsonBytes, MediaTypeJSON, "", block) + err = decodeSignedBlindedBeaconBlock(jsonBytes, MediaTypeJSON, consensusVersion, block) require.NoError(t, err) // Get the request slot and block hash @@ -1280,6 +1293,8 @@ func TestGetPayloadForks(t *testing.T) { payload = block.Deneb case spec.DataVersionElectra: payload = block.Electra + case spec.DataVersionFulu: + payload = block.Fulu case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair: require.Fail(t, "unsupported version") } @@ -1303,6 +1318,7 @@ func TestGetPayloadForks(t *testing.T) { func TestGetPayloadToAllRelays(t *testing.T) { header := make(http.Header) header.Set(HeaderAccept, MediaTypeJSON) + header.Set("Eth-Consensus-Version", "deneb") // Load the signed blinded beacon block used for getPayload jsonFile, err := os.Open("../testdata/signed-blinded-beacon-block-deneb.json") diff --git a/server/utils.go b/server/utils.go index 1e85da3c..06628840 100644 --- a/server/utils.go +++ b/server/utils.go @@ -223,6 +223,12 @@ func getPayloadResponseIsEmpty(payload *builderApi.VersionedSubmitBlindedBlockRe payload.Electra.BlobsBundle == nil { return true } + case spec.DataVersionFulu: + if payload.Fulu == nil || payload.Fulu.ExecutionPayload == nil || + payload.Fulu.ExecutionPayload.BlockHash == nilHash || + payload.Fulu.BlobsBundle == nil { + return true + } case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair: return true } From 39b6701bcde8bf0868d4b1b94d898175976d69b8 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 10 Apr 2025 16:12:27 -0500 Subject: [PATCH 02/10] Fix lint and some nits --- server/get_payload.go | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/server/get_payload.go b/server/get_payload.go index 7ca6fb35..b6d9017e 100644 --- a/server/get_payload.go +++ b/server/get_payload.go @@ -364,13 +364,13 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde // Check proofs responseProofs, err := responseBlobsBundle.Proofs() if err != nil { - log.WithError(err).Error("failed to get response blobs") + log.WithError(err).Error("failed to get response proofs") return err } if len(requestCommitments) != len(responseProofs) { log.WithFields(logrus.Fields{ "requestBlobCommitments": len(requestCommitments), - "responseProofss": len(responseProofs), + "responseProofs": len(responseProofs), }).Error("different lengths for proofs") return errInvalidKZGLength } @@ -467,44 +467,44 @@ func decodeSignedBlindedBeaconBlock(in []byte, contentType, ethConsensusVersion var err error switch ethConsensusVersion { case EthConsensusVersionBellatrix: - bellatrixBlock := new(eth2ApiV1Bellatrix.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, bellatrixBlock) - if err == nil { - out.Version = spec.DataVersionBellatrix - out.Bellatrix = bellatrixBlock + block := new(eth2ApiV1Bellatrix.SignedBlindedBeaconBlock) + if err = json.Unmarshal(in, block); err != nil { + return err } + out.Version = spec.DataVersionBellatrix + out.Bellatrix = block case EthConsensusVersionCapella: - capellaBlock := new(eth2ApiV1Capella.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, capellaBlock) - if err == nil { - out.Version = spec.DataVersionCapella - out.Capella = capellaBlock + block := new(eth2ApiV1Capella.SignedBlindedBeaconBlock) + if err = json.Unmarshal(in, block); err != nil { + return err } + out.Version = spec.DataVersionCapella + out.Capella = block case EthConsensusVersionDeneb: - denebBlock := new(eth2ApiV1Deneb.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, denebBlock) - if err == nil { - out.Version = spec.DataVersionDeneb - out.Deneb = denebBlock + block := new(eth2ApiV1Deneb.SignedBlindedBeaconBlock) + if err = json.Unmarshal(in, block); err != nil { + return err } + out.Version = spec.DataVersionDeneb + out.Deneb = block case EthConsensusVersionElectra: - electraBlock := new(eth2ApiV1Electra.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, electraBlock) - if err == nil { - out.Version = spec.DataVersionElectra - out.Electra = electraBlock + block := new(eth2ApiV1Electra.SignedBlindedBeaconBlock) + if err = json.Unmarshal(in, block); err != nil { + return err } + out.Version = spec.DataVersionElectra + out.Electra = block case EthConsensusVersionFulu: - fuluBlock := new(eth2ApiV1Electra.SignedBlindedBeaconBlock) - err = json.Unmarshal(in, fuluBlock) - if err == nil { - out.Version = spec.DataVersionFulu - out.Fulu = fuluBlock + block := new(eth2ApiV1Electra.SignedBlindedBeaconBlock) + if err = json.Unmarshal(in, block); err != nil { + return err } + out.Version = spec.DataVersionFulu + out.Fulu = block default: return errInvalidForkVersion } - return err + return nil } return types.ErrInvalidContentType } From 0fc73209241a6c9396eefc09babd225cf3c58e15 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 10 Apr 2025 16:31:40 -0500 Subject: [PATCH 03/10] Update go-eth2-client --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2987e4d4..865f0c72 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/attestantio/go-eth2-client => github.com/jtraglia/go-eth2-client v0.21.5-0.20250410190628-e845cec4674f +replace github.com/attestantio/go-eth2-client => github.com/jtraglia/go-eth2-client v0.21.5-0.20250410211702-92a5240d75c6 replace github.com/attestantio/go-builder-client => github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546 diff --git a/go.sum b/go.sum index 758274a7..367b2437 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0X github.com/huandu/go-clone/generic v1.6.0/go.mod h1:xgd9ZebcMsBWWcBx5mVMCoqMX24gLWr5lQicr+nVXNs= github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546 h1:UnMUnbUz6fiJ1Ox7hNMkAS6T+Vcdy2qjo9q2D3q8zqg= github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546/go.mod h1:3Z2KfjbuX2EmJajEmDb3I/JdDqzlm4WsojnCugO0oM0= -github.com/jtraglia/go-eth2-client v0.21.5-0.20250410190628-e845cec4674f h1:XacizBmPNKeJK/MdsbEUC/U6LVUlsuPsvgC8ia8QPBI= -github.com/jtraglia/go-eth2-client v0.21.5-0.20250410190628-e845cec4674f/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= +github.com/jtraglia/go-eth2-client v0.21.5-0.20250410211702-92a5240d75c6 h1:+rkagNgm3jRqhHxy1JA+PwhB5Jk+pJt9esk7C3l/Tvg= +github.com/jtraglia/go-eth2-client v0.21.5-0.20250410211702-92a5240d75c6/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= From d0240217aeda913665de713523867a82c7df2951 Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Thu, 10 Apr 2025 16:44:54 -0500 Subject: [PATCH 04/10] Add fulu test & fix tests --- server/service_test.go | 82 +++++++++++++++++-- .../signed-blinded-beacon-block-fulu.json | 72 ++++++++++++++++ 2 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 testdata/signed-blinded-beacon-block-fulu.json diff --git a/server/service_test.go b/server/service_test.go index 804e04f4..2ed240ad 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -18,6 +18,7 @@ import ( builderApi "github.com/attestantio/go-builder-client/api" builderApiDeneb "github.com/attestantio/go-builder-client/api/deneb" + builderApiFulu "github.com/attestantio/go-builder-client/api/fulu" builderApiV1 "github.com/attestantio/go-builder-client/api/v1" builderSpec "github.com/attestantio/go-builder-client/spec" eth2Api "github.com/attestantio/go-eth2-client/api" @@ -1134,10 +1135,13 @@ func TestEmptyTxRoot(t *testing.T) { require.Equal(t, "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1", txRootHex) } -func blindedBlockToBlockResponse(signedBlock any) *builderApi.VersionedSubmitBlindedBlockResponse { - // TODO(jtraglia): How do we update this to work with Fulu too? - switch block := signedBlock.(type) { - case *eth2ApiV1Bellatrix.SignedBlindedBeaconBlock: +func blindedBlockToBlockResponse(signedBlock any, version spec.DataVersion) *builderApi.VersionedSubmitBlindedBlockResponse { + switch version { + case spec.DataVersionBellatrix: + block, ok := signedBlock.(*eth2ApiV1Bellatrix.SignedBlindedBeaconBlock) + if !ok { + panic("failed to convert block") + } header := block.Message.Body.ExecutionPayloadHeader return &builderApi.VersionedSubmitBlindedBlockResponse{ Version: spec.DataVersionBellatrix, @@ -1158,7 +1162,11 @@ func blindedBlockToBlockResponse(signedBlock any) *builderApi.VersionedSubmitBli Transactions: make([]bellatrix.Transaction, 0), }, } - case *eth2ApiV1Capella.SignedBlindedBeaconBlock: + case spec.DataVersionCapella: + block, ok := signedBlock.(*eth2ApiV1Capella.SignedBlindedBeaconBlock) + if !ok { + panic("failed to convert block") + } header := block.Message.Body.ExecutionPayloadHeader return &builderApi.VersionedSubmitBlindedBlockResponse{ Version: spec.DataVersionCapella, @@ -1180,20 +1188,41 @@ func blindedBlockToBlockResponse(signedBlock any) *builderApi.VersionedSubmitBli Withdrawals: make([]*capella.Withdrawal, 0), }, } - case *eth2ApiV1Deneb.SignedBlindedBeaconBlock: + case spec.DataVersionDeneb: + block, ok := signedBlock.(*eth2ApiV1Deneb.SignedBlindedBeaconBlock) + if !ok { + panic("failed to convert block") + } header := block.Message.Body.ExecutionPayloadHeader commitments := block.Message.Body.BlobKZGCommitments return &builderApi.VersionedSubmitBlindedBlockResponse{ Version: spec.DataVersionDeneb, Deneb: denebExecutionPayloadAndBlobsBundle(header, commitments), } - case *eth2ApiV1Electra.SignedBlindedBeaconBlock: + case spec.DataVersionElectra: + block, ok := signedBlock.(*eth2ApiV1Electra.SignedBlindedBeaconBlock) + if !ok { + panic("failed to convert block") + } header := block.Message.Body.ExecutionPayloadHeader commitments := block.Message.Body.BlobKZGCommitments return &builderApi.VersionedSubmitBlindedBlockResponse{ Version: spec.DataVersionElectra, Electra: denebExecutionPayloadAndBlobsBundle(header, commitments), } + case spec.DataVersionFulu: + block, ok := signedBlock.(*eth2ApiV1Electra.SignedBlindedBeaconBlock) + if !ok { + panic("failed to convert block") + } + header := block.Message.Body.ExecutionPayloadHeader + commitments := block.Message.Body.BlobKZGCommitments + return &builderApi.VersionedSubmitBlindedBlockResponse{ + Version: spec.DataVersionFulu, + Fulu: fuluExecutionPayloadAndBlobsBundle(header, commitments), + } + case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair: + panic("unknown data version") } return nil } @@ -1233,6 +1262,41 @@ func denebExecutionPayloadAndBlobsBundle(header *deneb.ExecutionPayloadHeader, k } } +func fuluExecutionPayloadAndBlobsBundle(header *deneb.ExecutionPayloadHeader, kzgCommitments []deneb.KZGCommitment) *builderApiFulu.ExecutionPayloadAndBlobsBundle { + numBlobs := len(kzgCommitments) + commitments := make([]deneb.KZGCommitment, numBlobs) + copy(commitments, kzgCommitments) + // For testing, proofs and blobs are not populated + proofs := make([]deneb.KZGProof, numBlobs) + blobs := make([]deneb.Blob, numBlobs) + return &builderApiFulu.ExecutionPayloadAndBlobsBundle{ + ExecutionPayload: &deneb.ExecutionPayload{ + ParentHash: header.ParentHash, + FeeRecipient: header.FeeRecipient, + StateRoot: header.StateRoot, + ReceiptsRoot: header.ReceiptsRoot, + LogsBloom: header.LogsBloom, + PrevRandao: header.PrevRandao, + BlockNumber: header.BlockNumber, + GasLimit: header.GasLimit, + GasUsed: header.GasUsed, + Timestamp: header.Timestamp, + ExtraData: header.ExtraData, + BaseFeePerGas: header.BaseFeePerGas, + BlockHash: header.BlockHash, + Transactions: make([]bellatrix.Transaction, 0), + Withdrawals: make([]*capella.Withdrawal, 0), + BlobGasUsed: header.BlobGasUsed, + ExcessBlobGas: header.ExcessBlobGas, + }, + BlobsBundle: &builderApiFulu.BlobsBundle{ + Commitments: commitments, + Proofs: proofs, + Blobs: blobs, + }, + } +} + func TestGetPayloadForks(t *testing.T) { t.Parallel() @@ -1300,7 +1364,7 @@ func TestGetPayloadForks(t *testing.T) { } // Configure the relay's expected response and send the request - backend.relays[0].GetPayloadResponse = blindedBlockToBlockResponse(payload) + backend.relays[0].GetPayloadResponse = blindedBlockToBlockResponse(payload, block.Version) rr := backend.request(t, http.MethodPost, params.PathGetPayload, header, payload) // Validate the response @@ -1352,7 +1416,7 @@ func TestGetPayloadToAllRelays(t *testing.T) { require.Equal(t, 1, backend.relays[1].GetRequestCount(getHeaderPath)) // Prepare getPayload response - backend.relays[0].GetPayloadResponse = blindedBlockToBlockResponse(signedBlindedBeaconBlock) + backend.relays[0].GetPayloadResponse = blindedBlockToBlockResponse(signedBlindedBeaconBlock, spec.DataVersionDeneb) // call getPayload, ensure it's called to all relays rr = backend.request(t, http.MethodPost, params.PathGetPayload, header, signedBlindedBeaconBlock) diff --git a/testdata/signed-blinded-beacon-block-fulu.json b/testdata/signed-blinded-beacon-block-fulu.json new file mode 100644 index 00000000..2467a3b6 --- /dev/null +++ b/testdata/signed-blinded-beacon-block-fulu.json @@ -0,0 +1,72 @@ +{ + "message": { + "slot": "252288", + "proposer_index": "4295", + "parent_root": "0x9a8eef2096477e645150fee1a2ce13190382179a0ea843f31173715a1a14e074", + "state_root": "0x4c033e0b4a34c0eb8e0caba126fae3ed303a83254fe39f8daaa673125f4042cc", + "body": { + "randao_reveal": "0xa7a74e03d8ef909abc75b9452d167e15869180fb4b19db79a1136b510495f02d6ae480ee4ad6a2a9e41af866a9a54c681601a3a1dee30f676e93e6c6b3eb3e880c2cb8d32bd730ca9e7def92877c70da09bfc52a531f1be15619c8a3bb38bdf6", + "eth1_data": { + "deposit_root": "0x0cacd599c9cdcee8398b40ef045baf2c137bed4d2b02a465a0414b04015f861d", + "deposit_count": "216773", + "block_hash": "0xd75a680056c50b4e339eda2f91ccd33badc5d59feab830526e342c5ec68d8dce" + }, + "graffiti": "0x6c69676874686f7573652d6e65746865726d696e642d33000000000000000000", + "proposer_slashings": [], + "attester_slashings": [], + "attestations": [ + { + "aggregation_bits": "0xf8fbff9093195acfebcff69cdfef71fbd9eaf19777f7dfb1f9233faf08be7e463a675cefef2d9e03", + "data": { + "slot": "252287", + "index": "0", + "beacon_block_root": "0x9a8eef2096477e645150fee1a2ce13190382179a0ea843f31173715a1a14e074", + "source": { + "epoch": "7682", + "root": "0x83900465836d88fbd48a829ca207db86e32aa7797966df1b0c886128c72a1a0b" + }, + "target": { + "epoch": "7883", + "root": "0x6e20c4503b853781327d750ee818651e5493b04f5ea0f2699725eecb1e0c3ddf" + } + }, + "signature": "0xb8fb8248ce16152eb41f88803445ef64c33da86de7bfd398b12e745a449013f9454c38b42a658291e344e3eb11d1c3ec03d692b9ed299aff0f599ea9145596b5195d11ff49f83a573519616c8b76c9459a1ed5f869e1c5c6bad176adbd3b689c", + "committee_bits": "0x0300000000000000" + } + ], + "deposits": [], + "voluntary_exits": [], + "sync_aggregate": { + "sync_committee_bits": "0xde98bcde844ff76e87b94cfff1cbcc3dfbf93fdf3ee9b994764fafe484f762eb1562e7fa28e96f5d7a887bff689ffb932d5eff467e668d137bc565d37e3fa7fd", + "sync_committee_signature": "0x93a611fb577d17674242e42de06958513013b0cb07d1f284e993ed7d63ac43bbc234e54a886ca9cb979e76eabeeb6ee603556234b7d661bdb7a7ac98813028faf8060e5e9271d4f25de199ce5e2ecc2a6762a49c41bf366b1c4c54bc185c62f9" + }, + "execution_payload_header": { + "parent_hash": "0x98b62322edaa4d91ecc0847fe2f7debc89dec5e808d7e369aa9b535543227f60", + "fee_recipient": "0xf97e180c050e5ab072211ad2c213eb5aee4df134", + "state_root": "0x3b6b594d5cbe7ff4c8bdca04f080c57a18fffdaf1c8e700e69b86f7425ed8d73", + "receipts_root": "0x04deb4be6955e1a300123be48007597f67e4229f8ce70f4f10388de6fd3fa267", + "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prev_randao": "0x83612b3de54001f74bf36234e373c1fca95dab5e4645bc3faf7108345cf82e34", + "block_number": "220275", + "gas_limit": "30000000", + "gas_used": "84000", + "timestamp": "1732296378", + "extra_data": "0x4e65746865726d696e64", + "base_fee_per_gas": "7", + "block_hash": "0xb65b77e52407ff25f7fcd3f455c991ae67be1bc10cb7ec992a659698cf88870f", + "transactions_root": "0xb1fcd304d8ba402be6e76346395ea7641fbb4c83663b697a0e88ab115d859d3f", + "withdrawals_root": "0x792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535", + "blob_gas_used": "0", + "excess_blob_gas": "0" + }, + "bls_to_execution_changes": [], + "blob_kzg_commitments": [], + "execution_requests": { + "deposits": [], + "withdrawals": [], + "consolidations": [] + } + } + }, + "signature": "0x94cd72a70a0b424f68145115a9a52f6c8557fb40ec8b67c26cbb9b324b72756624a59e8bbe11b78acbbb8e9c606035d10c0dab9a3f4177d7e6954f8ea1863d0b0b00007fb420b4b4cf52e065bda0ad32af0d3a71bd6938180bab6dd1af754d2f" +} From 08e9d5c08298434375e18be42eb47a06165aef7c Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Thu, 26 Jun 2025 11:50:45 +0530 Subject: [PATCH 05/10] update go-eth2-client --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 865f0c72..dfb50f80 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( require ( github.com/attestantio/go-builder-client v0.6.1 - github.com/attestantio/go-eth2-client v0.25.0 + github.com/attestantio/go-eth2-client v0.25.1-0.20250603135601-6ac0bfda7fda github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect @@ -61,6 +61,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/attestantio/go-eth2-client => github.com/jtraglia/go-eth2-client v0.21.5-0.20250410211702-92a5240d75c6 - replace github.com/attestantio/go-builder-client => github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546 diff --git a/go.sum b/go.sum index 367b2437..438f6791 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/attestantio/go-eth2-client v0.25.1-0.20250603135601-6ac0bfda7fda h1:Wc7YGICtTWKNZvMjpIN/l6HKTj9POEU9N67NIWWWOk0= +github.com/attestantio/go-eth2-client v0.25.1-0.20250603135601-6ac0bfda7fda/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -57,8 +59,6 @@ github.com/huandu/go-clone/generic v1.6.0 h1:Wgmt/fUZ28r16F2Y3APotFD59sHk1p78K0X github.com/huandu/go-clone/generic v1.6.0/go.mod h1:xgd9ZebcMsBWWcBx5mVMCoqMX24gLWr5lQicr+nVXNs= github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546 h1:UnMUnbUz6fiJ1Ox7hNMkAS6T+Vcdy2qjo9q2D3q8zqg= github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546/go.mod h1:3Z2KfjbuX2EmJajEmDb3I/JdDqzlm4WsojnCugO0oM0= -github.com/jtraglia/go-eth2-client v0.21.5-0.20250410211702-92a5240d75c6 h1:+rkagNgm3jRqhHxy1JA+PwhB5Jk+pJt9esk7C3l/Tvg= -github.com/jtraglia/go-eth2-client v0.21.5-0.20250410211702-92a5240d75c6/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= From 481f298144b363ea227f9c399c179576a20386b9 Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Tue, 1 Jul 2025 10:29:21 +0530 Subject: [PATCH 06/10] ensure that number of commitments equals extended blob cell proofs --- common/common.go | 10 ++++++++++ server/get_payload.go | 27 ++++++++++++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/common/common.go b/common/common.go index 1af8e4fa..9ec5b544 100644 --- a/common/common.go +++ b/common/common.go @@ -10,6 +10,16 @@ import ( const ( SlotTimeSecMainnet = 12 + // FieldElementsPerBlob is the number of field elements needed to represent a blob. + FieldElementsPerBlob = 4096 + // BlobExpansionFactor is the factor by which we extend a blob for PeerDas. + BlobExpansionFactor = 2 + // FieldElementsPerCell is the number of field elements in a cell + FieldElementsPerCell = 64 + // FieldElementsPerExtBlob is the number of field elements needed to represent an extended blob. + FieldElementsPerExtBlob = FieldElementsPerBlob * BlobExpansionFactor + // CellsPerExtBlob is the number of cells in an extended blob. + CellsPerExtBlob = FieldElementsPerExtBlob / FieldElementsPerCell ) func GetEnv(key, defaultValue string) string { diff --git a/server/get_payload.go b/server/get_payload.go index bb1000f0..55af9858 100644 --- a/server/get_payload.go +++ b/server/get_payload.go @@ -25,6 +25,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/flashbots/mev-boost/common" "github.com/flashbots/mev-boost/config" "github.com/flashbots/mev-boost/server/params" "github.com/flashbots/mev-boost/server/types" @@ -338,7 +339,7 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde log.WithError(err).Error("failed to get response blobs bundle") return err } - + // Check commitments responseCommitments, err := responseBlobsBundle.Commitments() if err != nil { @@ -369,12 +370,24 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde log.WithError(err).Error("failed to get response proofs") return err } - if len(requestCommitments) != len(responseProofs) { - log.WithFields(logrus.Fields{ - "requestBlobCommitments": len(requestCommitments), - "responseProofs": len(responseProofs), - }).Error("different lengths for proofs") - return errInvalidKZGLength + + if request.Version >= spec.DataVersionFulu { + if len(requestCommitments) * common.CellsPerExtBlob != len(responseProofs) { + log.WithFields(logrus.Fields{ + "requestBlobCommitments": len(requestCommitments), + "responseProofs": len(responseProofs), + "cellsPerExtBlob": common.CellsPerExtBlob, + }).Error("different lengths for proofs") + return errInvalidKZGLength + } + } else { + if len(requestCommitments) != len(responseProofs) { + log.WithFields(logrus.Fields{ + "requestBlobCommitments": len(requestCommitments), + "responseProofs": len(responseProofs), + }).Error("different lengths for proofs") + return errInvalidKZGLength + } } // Check blobs From b8753f79867434aaea25419f113f36f6fe47639e Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Tue, 1 Jul 2025 10:34:25 +0530 Subject: [PATCH 07/10] fmt --- server/get_payload.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/get_payload.go b/server/get_payload.go index 55af9858..80ce1a9c 100644 --- a/server/get_payload.go +++ b/server/get_payload.go @@ -339,7 +339,7 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde log.WithError(err).Error("failed to get response blobs bundle") return err } - + // Check commitments responseCommitments, err := responseBlobsBundle.Commitments() if err != nil { @@ -372,7 +372,7 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde } if request.Version >= spec.DataVersionFulu { - if len(requestCommitments) * common.CellsPerExtBlob != len(responseProofs) { + if len(requestCommitments)*common.CellsPerExtBlob != len(responseProofs) { log.WithFields(logrus.Fields{ "requestBlobCommitments": len(requestCommitments), "responseProofs": len(responseProofs), From 866b905ad2fab34ad413914c22377891d5558d5c Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Mon, 14 Jul 2025 11:18:55 +0530 Subject: [PATCH 08/10] minor typos --- common/common.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/common.go b/common/common.go index 9ec5b544..05858d58 100644 --- a/common/common.go +++ b/common/common.go @@ -12,9 +12,9 @@ const ( SlotTimeSecMainnet = 12 // FieldElementsPerBlob is the number of field elements needed to represent a blob. FieldElementsPerBlob = 4096 - // BlobExpansionFactor is the factor by which we extend a blob for PeerDas. + // BlobExpansionFactor is the factor by which we extend a blob for PeerDAS. BlobExpansionFactor = 2 - // FieldElementsPerCell is the number of field elements in a cell + // FieldElementsPerCell is the number of field elements in a cell. FieldElementsPerCell = 64 // FieldElementsPerExtBlob is the number of field elements needed to represent an extended blob. FieldElementsPerExtBlob = FieldElementsPerBlob * BlobExpansionFactor From eb4354705a68ef2e64bd0a82f43eb14bcb10b820 Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Tue, 15 Jul 2025 09:35:15 +0530 Subject: [PATCH 09/10] address some review comments --- server/get_payload.go | 8 ++++---- server/service_test.go | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/server/get_payload.go b/server/get_payload.go index 80ce1a9c..d990699e 100644 --- a/server/get_payload.go +++ b/server/get_payload.go @@ -350,7 +350,7 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde log.WithFields(logrus.Fields{ "requestBlobCommitments": len(requestCommitments), "responseCommitments": len(responseCommitments), - }).Error("different lengths for commitments") + }).Error("wrong lengths for commitments") return errInvalidKZGLength } for i, commitment := range requestCommitments { @@ -377,7 +377,7 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde "requestBlobCommitments": len(requestCommitments), "responseProofs": len(responseProofs), "cellsPerExtBlob": common.CellsPerExtBlob, - }).Error("different lengths for proofs") + }).Error("wrong lengths for proofs") return errInvalidKZGLength } } else { @@ -385,7 +385,7 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde log.WithFields(logrus.Fields{ "requestBlobCommitments": len(requestCommitments), "responseProofs": len(responseProofs), - }).Error("different lengths for proofs") + }).Error("wrong lengths for proofs") return errInvalidKZGLength } } @@ -400,7 +400,7 @@ func verifyBlobsBundle(log *logrus.Entry, request *eth2Api.VersionedSignedBlinde log.WithFields(logrus.Fields{ "requestBlobCommitments": len(requestCommitments), "responseBlobs": len(responseBlobs), - }).Error("different lengths for blobs") + }).Error("wrong lengths for blobs") return errInvalidKZGLength } diff --git a/server/service_test.go b/server/service_test.go index b429e4e7..2bdb1c05 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -34,6 +34,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" eth2UtilBellatrix "github.com/attestantio/go-eth2-client/util/bellatrix" + "github.com/flashbots/mev-boost/common" "github.com/flashbots/mev-boost/server/mock" "github.com/flashbots/mev-boost/server/params" "github.com/flashbots/mev-boost/server/types" @@ -1313,7 +1314,7 @@ func fuluExecutionPayloadAndBlobsBundle(header *deneb.ExecutionPayloadHeader, kz commitments := make([]deneb.KZGCommitment, numBlobs) copy(commitments, kzgCommitments) // For testing, proofs and blobs are not populated - proofs := make([]deneb.KZGProof, numBlobs) + proofs := make([]deneb.KZGProof, numBlobs * common.CellsPerExtBlob) blobs := make([]deneb.Blob, numBlobs) return &builderApiFulu.ExecutionPayloadAndBlobsBundle{ ExecutionPayload: &deneb.ExecutionPayload{ From acb775dae4676526fd396247033e69374b536a6a Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Tue, 15 Jul 2025 11:16:21 +0530 Subject: [PATCH 10/10] add blobs, commitments and cell proofs to fulu payload tests --- go.mod | 5 ++-- go.sum | 10 ++++---- server/service_test.go | 52 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index dfb50f80..3996a72d 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,10 @@ require ( github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/consensys/bavard v0.1.30 // indirect github.com/consensys/gnark-crypto v0.17.0 // indirect + github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect - github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect github.com/emicklei/dot v1.8.0 // indirect - github.com/ethereum/c-kzg-4844 v1.0.3 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.0.1 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/goccy/go-yaml v1.17.1 // indirect github.com/gofrs/flock v0.12.1 // indirect @@ -62,3 +62,4 @@ require ( ) replace github.com/attestantio/go-builder-client => github.com/jtraglia/go-builder-client v0.4.6-0.20250410195459-42a3ff7f1546 +replace github.com/ethereum/go-ethereum => github.com/MariusVanDerWijden/go-ethereum v0.0.0-20250401090816-5ab5127f5408 diff --git a/go.sum b/go.sum index 438f6791..878beac9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/MariusVanDerWijden/go-ethereum v0.0.0-20250401090816-5ab5127f5408 h1:0H1RpU0pilwhz2sItGYkA1z8+i4MJSf0u/kH5vQloTk= +github.com/MariusVanDerWijden/go-ethereum v0.0.0-20250401090816-5ab5127f5408/go.mod h1:huOmsndLTH6QOh8JLawPM5qPrQbdQ5QlD2wd5AN9VTI= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/attestantio/go-eth2-client v0.25.1-0.20250603135601-6ac0bfda7fda h1:Wc7YGICtTWKNZvMjpIN/l6HKTj9POEU9N67NIWWWOk0= @@ -10,6 +12,8 @@ github.com/consensys/bavard v0.1.30 h1:wwAj9lSnMLFXjEclKwyhf7Oslg8EoaFz9u1QGgt0b github.com/consensys/bavard v0.1.30/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= github.com/consensys/gnark-crypto v0.17.0 h1:vKDhZMOrySbpZDCvGMOELrHFv/A9mJ7+9I8HEfRZSkI= github.com/consensys/gnark-crypto v0.17.0/go.mod h1:A2URlMHUT81ifJ0UlLzSlm7TmnE3t7VxEThApdMukJw= +github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI= +github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= @@ -23,10 +27,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/emicklei/dot v1.8.0 h1:HnD60yAKFAevNeT+TPYr9pb8VB9bqdeSo0nzwIW6IOI= github.com/emicklei/dot v1.8.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= -github.com/ethereum/c-kzg-4844 v1.0.3 h1:IEnbOHwjixW2cTvKRUlAAUOeleV7nNM/umJR+qy4WDs= -github.com/ethereum/c-kzg-4844 v1.0.3/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.15.9 h1:bRra1zi+/q+qyXZ6fylZOrlaF8kDdnlTtzNTmNHfX+g= -github.com/ethereum/go-ethereum v1.15.9/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= +github.com/ethereum/c-kzg-4844/v2 v2.0.1 h1:NuErvd0Ha5gLvvZ1m9Id9UZ11kcqMBUUXsbm7yXcAYI= +github.com/ethereum/c-kzg-4844/v2 v2.0.1/go.mod h1:urP+cLBtKCW4BS5bnA2IXYs1PRGPpXmdotqpBuU6/5s= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= diff --git a/server/service_test.go b/server/service_test.go index 2bdb1c05..fcbdadce 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -2,6 +2,7 @@ package server import ( "bytes" + "crypto/rand" "encoding/json" "fmt" "io" @@ -34,6 +35,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" eth2UtilBellatrix "github.com/attestantio/go-eth2-client/util/bellatrix" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/flashbots/mev-boost/common" "github.com/flashbots/mev-boost/server/mock" "github.com/flashbots/mev-boost/server/params" @@ -1281,6 +1283,9 @@ func denebExecutionPayloadAndBlobsBundle(header *deneb.ExecutionPayloadHeader, k // For testing, proofs and blobs are not populated proofs := make([]deneb.KZGProof, numBlobs) blobs := make([]deneb.Blob, numBlobs) + + // TODO - add blobs, commitments and proofs for deneb + return &builderApiDeneb.ExecutionPayloadAndBlobsBundle{ ExecutionPayload: &deneb.ExecutionPayload{ ParentHash: header.ParentHash, @@ -1314,8 +1319,38 @@ func fuluExecutionPayloadAndBlobsBundle(header *deneb.ExecutionPayloadHeader, kz commitments := make([]deneb.KZGCommitment, numBlobs) copy(commitments, kzgCommitments) // For testing, proofs and blobs are not populated - proofs := make([]deneb.KZGProof, numBlobs * common.CellsPerExtBlob) + proofs := make([]deneb.KZGProof, 0, numBlobs * common.CellsPerExtBlob) blobs := make([]deneb.Blob, numBlobs) + + for i := 0; i < numBlobs; i++ { + blobData, err := randomBlobData(deneb.BlobLength) + if err != nil { + panic(err) + } + blobs[i] = deneb.Blob(blobData) + } + // generate kzg commitments + for i := 0; i < numBlobs; i++ { + convertedBlob := kzg4844.Blob(blobs[i]) + commitment, err := kzg4844.BlobToCommitment(&convertedBlob) + if err != nil { + panic(err) + } + commitments[i] = deneb.KZGCommitment(commitment) + } + for i := 0; i < numBlobs; i++ { + convertedBlob := kzg4844.Blob(blobs[i]) + proof, err := kzg4844.ComputeCells(&convertedBlob) + if err != nil { + panic(err) + } + denebProofs := make([]deneb.KZGProof, len(proof)) + for j := 0; j < len(proof); j++ { + denebProofs[j] = deneb.KZGProof(proof[j]) + } + proofs = append(proofs, denebProofs...) + } + return &builderApiFulu.ExecutionPayloadAndBlobsBundle{ ExecutionPayload: &deneb.ExecutionPayload{ ParentHash: header.ParentHash, @@ -1344,6 +1379,21 @@ func fuluExecutionPayloadAndBlobsBundle(header *deneb.ExecutionPayloadHeader, kz } } +// randomBlobData generates cryptographically secure random blob data of the specified size. +// Uses crypto/rand for secure random number generation. +// Returns an error if the random data generation fails or doesn't produce the requested size. +func randomBlobData(size int) ([]byte, error) { + data := make([]byte, size) + n, err := rand.Read(data) + if err != nil { + return nil, err + } + if n != size { + return nil, fmt.Errorf("could not create random blob data with size %d: %v", size, err) + } + return data, nil +} + func TestGetPayloadForks(t *testing.T) { t.Parallel()