diff --git a/CHANGELOG.md b/CHANGELOG.md index c4463e28efb4..a01a870a659d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Added GetBlockAttestationsV2 endpoint. - Light client support: Consensus types for Electra - Added SubmitPoolAttesterSlashingV2 endpoint. +- Added SubmitAggregateAndProofsRequestV2 endpoint. ### Changed diff --git a/api/server/structs/endpoints_validator.go b/api/server/structs/endpoints_validator.go index dfb94daea20a..8fc4b7d83fae 100644 --- a/api/server/structs/endpoints_validator.go +++ b/api/server/structs/endpoints_validator.go @@ -15,7 +15,7 @@ type SubmitContributionAndProofsRequest struct { } type SubmitAggregateAndProofsRequest struct { - Data []*SignedAggregateAttestationAndProof `json:"data"` + Data []json.RawMessage `json:"data"` } type SubmitSyncCommitteeSubscriptionsRequest struct { diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index f6b47009582b..2d61aee6173c 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -219,6 +219,16 @@ func (s *Service) validatorEndpoints( handler: server.SubmitAggregateAndProofs, methods: []string{http.MethodPost}, }, + { + template: "/eth/v2/validator/aggregate_and_proofs", + name: namespace + ".SubmitAggregateAndProofsV2", + middleware: []middleware.Middleware{ + middleware.ContentTypeHandler([]string{api.JsonMediaType}), + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.SubmitAggregateAndProofsV2, + methods: []string{http.MethodPost}, + }, { template: "/eth/v1/validator/sync_committee_contribution", name: namespace + ".ProduceSyncCommitteeContribution", diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index d77dc4257b59..463bf9126667 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -101,6 +101,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/validator/attestation_data": {http.MethodGet}, "/eth/v1/validator/aggregate_attestation": {http.MethodGet}, "/eth/v1/validator/aggregate_and_proofs": {http.MethodPost}, + "/eth/v2/validator/aggregate_and_proofs": {http.MethodPost}, "/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost}, "/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost}, "/eth/v1/validator/beacon_committee_selections": {http.MethodPost}, diff --git a/beacon-chain/rpc/eth/validator/BUILD.bazel b/beacon-chain/rpc/eth/validator/BUILD.bazel index dc941ae72578..ed434774d47d 100644 --- a/beacon-chain/rpc/eth/validator/BUILD.bazel +++ b/beacon-chain/rpc/eth/validator/BUILD.bazel @@ -85,6 +85,7 @@ go_test( "//encoding/bytesutil:go_default_library", "//network/httputil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/mock:go_default_library", "//testing/require:go_default_library", diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 597af22476a6..5fc5c68e3e78 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api/server/structs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/builder" "github.com/prysmaticlabs/prysm/v5/beacon-chain/cache" @@ -31,6 +32,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" "github.com/prysmaticlabs/prysm/v5/network/httputil" ethpbalpha "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" @@ -118,30 +120,34 @@ func (s *Server) SubmitContributionAndProofs(w http.ResponseWriter, r *http.Requ ctx, span := trace.StartSpan(r.Context(), "validator.SubmitContributionAndProofs") defer span.End() - var req structs.SubmitContributionAndProofsRequest - err := json.NewDecoder(r.Body).Decode(&req.Data) - switch { - case errors.Is(err, io.EOF): - httputil.HandleError(w, "No data submitted", http.StatusBadRequest) - return - case err != nil: - httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + var reqData []json.RawMessage + if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { + if errors.Is(err, io.EOF) { + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + } else { + httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + } return } - if len(req.Data) == 0 { + if len(reqData) == 0 { httputil.HandleError(w, "No data submitted", http.StatusBadRequest) return } - for _, item := range req.Data { - consensusItem, err := item.ToConsensus() + for _, item := range reqData { + var contribution structs.SignedContributionAndProof + if err := json.Unmarshal(item, &contribution); err != nil { + httputil.HandleError(w, "Could not decode item: "+err.Error(), http.StatusBadRequest) + return + } + consensusItem, err := contribution.ToConsensus() if err != nil { - httputil.HandleError(w, "Could not convert request contribution to consensus contribution: "+err.Error(), http.StatusBadRequest) + httputil.HandleError(w, "Could not convert contribution to consensus format: "+err.Error(), http.StatusBadRequest) return } - rpcError := s.CoreService.SubmitSignedContributionAndProof(ctx, consensusItem) - if rpcError != nil { + if rpcError := s.CoreService.SubmitSignedContributionAndProof(ctx, consensusItem); rpcError != nil { httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason)) + return } } } @@ -168,7 +174,13 @@ func (s *Server) SubmitAggregateAndProofs(w http.ResponseWriter, r *http.Request broadcastFailed := false for _, item := range req.Data { - consensusItem, err := item.ToConsensus() + var signedAggregate structs.SignedAggregateAttestationAndProof + err := json.Unmarshal(item, &signedAggregate) + if err != nil { + httputil.HandleError(w, "Could not decode item: "+err.Error(), http.StatusBadRequest) + return + } + consensusItem, err := signedAggregate.ToConsensus() if err != nil { httputil.HandleError(w, "Could not convert request aggregate to consensus aggregate: "+err.Error(), http.StatusBadRequest) return @@ -191,6 +203,81 @@ func (s *Server) SubmitAggregateAndProofs(w http.ResponseWriter, r *http.Request } } +// SubmitAggregateAndProofsV2 verifies given aggregate and proofs and publishes them on appropriate gossipsub topic. +func (s *Server) SubmitAggregateAndProofsV2(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "validator.SubmitAggregateAndProofsV2") + defer span.End() + + var reqData []json.RawMessage + if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil { + if errors.Is(err, io.EOF) { + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + } else { + httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest) + } + return + } + if len(reqData) == 0 { + httputil.HandleError(w, "No data submitted", http.StatusBadRequest) + return + } + + versionHeader := r.Header.Get(api.VersionHeader) + if versionHeader == "" { + httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest) + } + v, err := version.FromString(versionHeader) + if err != nil { + httputil.HandleError(w, "Invalid version: "+err.Error(), http.StatusBadRequest) + return + } + + broadcastFailed := false + var rpcError *core.RpcError + for _, raw := range reqData { + if v >= version.Electra { + var signedAggregate structs.SignedAggregateAttestationAndProofElectra + err = json.Unmarshal(raw, &signedAggregate) + if err != nil { + httputil.HandleError(w, "Failed to parse aggregate attestation and proof: "+err.Error(), http.StatusBadRequest) + return + } + consensusItem, err := signedAggregate.ToConsensus() + if err != nil { + httputil.HandleError(w, "Could not convert request aggregate to consensus aggregate: "+err.Error(), http.StatusBadRequest) + return + } + rpcError = s.CoreService.SubmitSignedAggregateSelectionProof(ctx, consensusItem) + } else { + var signedAggregate structs.SignedAggregateAttestationAndProof + err = json.Unmarshal(raw, &signedAggregate) + if err != nil { + httputil.HandleError(w, "Failed to parse aggregate attestation and proof: "+err.Error(), http.StatusBadRequest) + return + } + consensusItem, err := signedAggregate.ToConsensus() + if err != nil { + httputil.HandleError(w, "Could not convert request aggregate to consensus aggregate: "+err.Error(), http.StatusBadRequest) + return + } + rpcError = s.CoreService.SubmitSignedAggregateSelectionProof(ctx, consensusItem) + } + + if rpcError != nil { + var aggregateBroadcastFailedError *core.AggregateBroadcastFailedError + if errors.As(rpcError.Err, &aggregateBroadcastFailedError) { + broadcastFailed = true + } else { + httputil.HandleError(w, rpcError.Err.Error(), core.ErrorReasonToHTTP(rpcError.Reason)) + return + } + } + } + if broadcastFailed { + httputil.HandleError(w, "Could not broadcast one or more signed aggregated attestations", http.StatusInternalServerError) + } +} + // SubmitSyncCommitteeSubscription subscribe to a number of sync committee subnets. // // Subscribing to sync committee subnets is an action performed by VC to enable diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index 0aa758fe48b8..5b5d2b101116 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/api" "github.com/prysmaticlabs/prysm/v5/api/server/structs" mockChain "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" builderTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/builder/testing" @@ -37,6 +38,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v5/network/httputil" ethpbalpha "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" @@ -427,85 +429,238 @@ func TestSubmitContributionAndProofs(t *testing.T) { } func TestSubmitAggregateAndProofs(t *testing.T) { - c := &core.Service{ - GenesisTimeFetcher: &mockChain.ChainService{}, - } - s := &Server{ - CoreService: c, + CoreService: &core.Service{GenesisTimeFetcher: &mockChain.ChainService{}}, } + t.Run("V1", func(t *testing.T) { + t.Run("single", func(t *testing.T) { + broadcaster := &p2pmock.MockBroadcaster{} + s.CoreService.Broadcaster = broadcaster - t.Run("single", func(t *testing.T) { - broadcaster := &p2pmock.MockBroadcaster{} - c.Broadcaster = broadcaster + var body bytes.Buffer + _, err := body.WriteString(singleAggregate) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - var body bytes.Buffer - _, err := body.WriteString(singleAggregate) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s.SubmitAggregateAndProofs(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + assert.Equal(t, 1, len(broadcaster.BroadcastMessages)) + }) + t.Run("multiple", func(t *testing.T) { + broadcaster := &p2pmock.MockBroadcaster{} + s.CoreService.Broadcaster = broadcaster + s.CoreService.SyncCommitteePool = synccommittee.NewStore() - s.SubmitAggregateAndProofs(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - assert.Equal(t, 1, len(broadcaster.BroadcastMessages)) - }) - t.Run("multiple", func(t *testing.T) { - broadcaster := &p2pmock.MockBroadcaster{} - c.Broadcaster = broadcaster - c.SyncCommitteePool = synccommittee.NewStore() + var body bytes.Buffer + _, err := body.WriteString(multipleAggregates) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - var body bytes.Buffer - _, err := body.WriteString(multipleAggregates) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s.SubmitAggregateAndProofs(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + assert.Equal(t, 2, len(broadcaster.BroadcastMessages)) + }) + t.Run("no body", func(t *testing.T) { + request := httptest.NewRequest(http.MethodPost, "http://example.com", nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofs(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) + }) + t.Run("empty", func(t *testing.T) { + var body bytes.Buffer + _, err := body.WriteString("[]") + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofs(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) + }) + t.Run("invalid", func(t *testing.T) { + var body bytes.Buffer + _, err := body.WriteString(invalidAggregate) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - s.SubmitAggregateAndProofs(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - assert.Equal(t, 2, len(broadcaster.BroadcastMessages)) + s.SubmitAggregateAndProofs(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + }) }) - t.Run("no body", func(t *testing.T) { - request := httptest.NewRequest(http.MethodPost, "http://example.com", nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + t.Run("V2", func(t *testing.T) { + t.Run("single", func(t *testing.T) { + broadcaster := &p2pmock.MockBroadcaster{} + s.CoreService.Broadcaster = broadcaster - s.SubmitAggregateAndProofs(writer, request) - assert.Equal(t, http.StatusBadRequest, writer.Code) - e := &httputil.DefaultJsonError{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) - assert.Equal(t, http.StatusBadRequest, e.Code) - assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) - }) - t.Run("empty", func(t *testing.T) { - var body bytes.Buffer - _, err := body.WriteString("[]") - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + var body bytes.Buffer + _, err := body.WriteString(singleAggregateElectra) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - s.SubmitAggregateAndProofs(writer, request) - assert.Equal(t, http.StatusBadRequest, writer.Code) - e := &httputil.DefaultJsonError{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) - assert.Equal(t, http.StatusBadRequest, e.Code) - assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) - }) - t.Run("invalid", func(t *testing.T) { - var body bytes.Buffer - _, err := body.WriteString(invalidAggregate) - require.NoError(t, err) - request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + assert.Equal(t, 1, len(broadcaster.BroadcastMessages)) + }) + t.Run("single-pre-electra", func(t *testing.T) { + broadcaster := &p2pmock.MockBroadcaster{} + s.CoreService.Broadcaster = broadcaster - s.SubmitAggregateAndProofs(writer, request) - assert.Equal(t, http.StatusBadRequest, writer.Code) - e := &httputil.DefaultJsonError{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) - assert.Equal(t, http.StatusBadRequest, e.Code) + var body bytes.Buffer + _, err := body.WriteString(singleAggregate) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Phase0)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + assert.Equal(t, 1, len(broadcaster.BroadcastMessages)) + }) + t.Run("multiple", func(t *testing.T) { + broadcaster := &p2pmock.MockBroadcaster{} + s.CoreService.Broadcaster = broadcaster + s.CoreService.SyncCommitteePool = synccommittee.NewStore() + + var body bytes.Buffer + _, err := body.WriteString(multipleAggregatesElectra) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + assert.Equal(t, 2, len(broadcaster.BroadcastMessages)) + }) + t.Run("multiple-pre-electra", func(t *testing.T) { + broadcaster := &p2pmock.MockBroadcaster{} + s.CoreService.Broadcaster = broadcaster + s.CoreService.SyncCommitteePool = synccommittee.NewStore() + + var body bytes.Buffer + _, err := body.WriteString(multipleAggregates) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Phase0)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + assert.Equal(t, 2, len(broadcaster.BroadcastMessages)) + }) + t.Run("no body", func(t *testing.T) { + request := httptest.NewRequest(http.MethodPost, "http://example.com", nil) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) + }) + t.Run("no body-pre-electra", func(t *testing.T) { + request := httptest.NewRequest(http.MethodPost, "http://example.com", nil) + request.Header.Set(api.VersionHeader, version.String(version.Bellatrix)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) + }) + t.Run("empty", func(t *testing.T) { + var body bytes.Buffer + _, err := body.WriteString("[]") + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) + }) + t.Run("empty-pre-electra", func(t *testing.T) { + var body bytes.Buffer + _, err := body.WriteString("[]") + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Altair)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No data submitted")) + }) + t.Run("invalid", func(t *testing.T) { + var body bytes.Buffer + _, err := body.WriteString(invalidAggregateElectra) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Electra)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + }) + t.Run("invalid-pre-electra", func(t *testing.T) { + var body bytes.Buffer + _, err := body.WriteString(invalidAggregate) + require.NoError(t, err) + request := httptest.NewRequest(http.MethodPost, "http://example.com", &body) + request.Header.Set(api.VersionHeader, version.String(version.Deneb)) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.SubmitAggregateAndProofsV2(writer, request) + assert.Equal(t, http.StatusBadRequest, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusBadRequest, e.Code) + }) }) } @@ -2766,6 +2921,115 @@ var ( "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" } ]` + + singleAggregateElectra = `[ + { + "message": { + "aggregator_index": "1", + "aggregate": { + "aggregation_bits": "0x01", + "committee_bits": "0x01", + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "data": { + "slot": "1", + "index": "1", + "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "source": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + }, + "target": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + } + } + }, + "selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + }, + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + } +]` + multipleAggregatesElectra = `[ + { + "message": { + "aggregator_index": "1", + "aggregate": { + "aggregation_bits": "0x01", + "committee_bits": "0x01", + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "data": { + "slot": "1", + "index": "1", + "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "source": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + }, + "target": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + } + } + }, + "selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + }, + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + }, +{ + "message": { + "aggregator_index": "1", + "aggregate": { + "aggregation_bits": "0x01", + "committee_bits": "0x01", + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "data": { + "slot": "1", + "index": "1", + "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "source": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + }, + "target": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + } + } + }, + "selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + }, + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + } +] +` + // aggregator_index is invalid + invalidAggregateElectra = `[ + { + "message": { + "aggregator_index": "foo", + "aggregate": { + "aggregation_bits": "0x01", + "committee_bits": "0x01", + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", + "data": { + "slot": "1", + "index": "1", + "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", + "source": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + }, + "target": { + "epoch": "1", + "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + } + } + }, + "selection_proof": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + }, + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + } +]` singleSyncCommitteeSubscription = `[ { "validator_index": "1",