From 09accc71320bfb2af01f9c6871fe003abe7d7fe2 Mon Sep 17 00:00:00 2001 From: Sammy Rosso <15244892+saolyn@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:59:27 +0100 Subject: [PATCH] Add `GET /eth/v2/beacon/pool/attestations` endpoint (#14560) * add ListAttestationsV2 endpoint * fix endpoint * changelog * add endpoint to tests * add trailing comma * add version header + lint fix * all reviews * modify v1 and comments * fix linter * Radek' review --- CHANGELOG.md | 1 + api/server/structs/endpoints_beacon.go | 3 +- beacon-chain/rpc/endpoints.go | 9 + beacon-chain/rpc/endpoints_test.go | 1 + beacon-chain/rpc/eth/beacon/handlers_pool.go | 131 ++++-- .../rpc/eth/beacon/handlers_pool_test.go | 407 +++++++++++++++--- config/params/config.go | 4 +- 7 files changed, 463 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d842d57b40d0..e9535921b5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Added SubmitPoolAttesterSlashingV2 endpoint. - Added SubmitAggregateAndProofsRequestV2 endpoint. - Updated the `beacon-chain/monitor` package to Electra. [PR](https://github.com/prysmaticlabs/prysm/pull/14562) +- Added ListAttestationsV2 endpoint. ### Changed diff --git a/api/server/structs/endpoints_beacon.go b/api/server/structs/endpoints_beacon.go index aff21aad55a7..273e95785307 100644 --- a/api/server/structs/endpoints_beacon.go +++ b/api/server/structs/endpoints_beacon.go @@ -21,7 +21,8 @@ type GetCommitteesResponse struct { } type ListAttestationsResponse struct { - Data []*Attestation `json:"data"` + Version string `json:"version,omitempty"` + Data json.RawMessage `json:"data"` } type SubmitAttestationsRequest struct { diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index ecdb141ebc8b..cdd46f11cec0 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -631,6 +631,15 @@ func (s *Service) beaconEndpoints( handler: server.ListAttestations, methods: []string{http.MethodGet}, }, + { + template: "/eth/v2/beacon/pool/attestations", + name: namespace + ".ListAttestationsV2", + middleware: []middleware.Middleware{ + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.ListAttestationsV2, + methods: []string{http.MethodGet}, + }, { template: "/eth/v1/beacon/pool/attestations", name: namespace + ".SubmitAttestations", diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index 463bf9126667..3270327ff535 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -41,6 +41,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/beacon/deposit_snapshot": {http.MethodGet}, "/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet}, "/eth/v1/beacon/pool/attestations": {http.MethodGet, http.MethodPost}, + "/eth/v2/beacon/pool/attestations": {http.MethodGet}, "/eth/v1/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost}, "/eth/v2/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost}, "/eth/v1/beacon/pool/proposer_slashings": {http.MethodGet, http.MethodPost}, diff --git a/beacon-chain/rpc/eth/beacon/handlers_pool.go b/beacon-chain/rpc/eth/beacon/handlers_pool.go index 96c6f405f0e7..6755e85d7941 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_pool.go +++ b/beacon-chain/rpc/eth/beacon/handlers_pool.go @@ -55,39 +55,122 @@ func (s *Server) ListAttestations(w http.ResponseWriter, r *http.Request) { return } attestations = append(attestations, unaggAtts...) - isEmptyReq := rawSlot == "" && rawCommitteeIndex == "" - if isEmptyReq { - allAtts := make([]*structs.Attestation, len(attestations)) - for i, att := range attestations { - a, ok := att.(*eth.Attestation) - if ok { - allAtts[i] = structs.AttFromConsensus(a) - } else { - httputil.HandleError(w, fmt.Sprintf("unable to convert attestations of type %T", att), http.StatusInternalServerError) - return - } + + filteredAtts := make([]*structs.Attestation, 0, len(attestations)) + for _, a := range attestations { + var includeAttestation bool + att, ok := a.(*eth.Attestation) + if !ok { + httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", a), http.StatusInternalServerError) + return + } + + includeAttestation = shouldIncludeAttestation(att.GetData(), rawSlot, slot, rawCommitteeIndex, committeeIndex) + if includeAttestation { + attStruct := structs.AttFromConsensus(att) + filteredAtts = append(filteredAtts, attStruct) } - httputil.WriteJson(w, &structs.ListAttestationsResponse{Data: allAtts}) + } + + attsData, err := json.Marshal(filteredAtts) + if err != nil { + httputil.HandleError(w, "Could not marshal attestations: "+err.Error(), http.StatusInternalServerError) return } - bothDefined := rawSlot != "" && rawCommitteeIndex != "" - filteredAtts := make([]*structs.Attestation, 0, len(attestations)) + httputil.WriteJson(w, &structs.ListAttestationsResponse{ + Data: attsData, + }) +} + +// ListAttestationsV2 retrieves attestations known by the node but +// not necessarily incorporated into any block. Allows filtering by committee index or slot. +func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) { + ctx, span := trace.StartSpan(r.Context(), "beacon.ListAttestationsV2") + defer span.End() + + rawSlot, slot, ok := shared.UintFromQuery(w, r, "slot", false) + if !ok { + return + } + rawCommitteeIndex, committeeIndex, ok := shared.UintFromQuery(w, r, "committee_index", false) + if !ok { + return + } + + headState, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx) + if err != nil { + httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError) + return + } + + attestations := s.AttestationsPool.AggregatedAttestations() + unaggAtts, err := s.AttestationsPool.UnaggregatedAttestations() + if err != nil { + httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError) + return + } + attestations = append(attestations, unaggAtts...) + + filteredAtts := make([]interface{}, 0, len(attestations)) for _, att := range attestations { - committeeIndexMatch := rawCommitteeIndex != "" && att.GetData().CommitteeIndex == primitives.CommitteeIndex(committeeIndex) - slotMatch := rawSlot != "" && att.GetData().Slot == primitives.Slot(slot) - shouldAppend := (bothDefined && committeeIndexMatch && slotMatch) || (!bothDefined && (committeeIndexMatch || slotMatch)) - if shouldAppend { - a, ok := att.(*eth.Attestation) - if ok { - filteredAtts = append(filteredAtts, structs.AttFromConsensus(a)) - } else { - httputil.HandleError(w, fmt.Sprintf("unable to convert attestations of type %T", att), http.StatusInternalServerError) + var includeAttestation bool + if headState.Version() >= version.Electra { + attElectra, ok := att.(*eth.AttestationElectra) + if !ok { + httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError) + return + } + + includeAttestation = shouldIncludeAttestation(attElectra.GetData(), rawSlot, slot, rawCommitteeIndex, committeeIndex) + if includeAttestation { + attStruct := structs.AttElectraFromConsensus(attElectra) + filteredAtts = append(filteredAtts, attStruct) + } + } else { + attOld, ok := att.(*eth.Attestation) + if !ok { + httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError) return } + + includeAttestation = shouldIncludeAttestation(attOld.GetData(), rawSlot, slot, rawCommitteeIndex, committeeIndex) + if includeAttestation { + attStruct := structs.AttFromConsensus(attOld) + filteredAtts = append(filteredAtts, attStruct) + } } } - httputil.WriteJson(w, &structs.ListAttestationsResponse{Data: filteredAtts}) + + attsData, err := json.Marshal(filteredAtts) + if err != nil { + httputil.HandleError(w, "Could not marshal attestations: "+err.Error(), http.StatusInternalServerError) + return + } + + httputil.WriteJson(w, &structs.ListAttestationsResponse{ + Version: version.String(headState.Version()), + Data: attsData, + }) +} + +// Helper function to determine if an attestation should be included +func shouldIncludeAttestation( + data *eth.AttestationData, + rawSlot string, + slot uint64, + rawCommitteeIndex string, + committeeIndex uint64, +) bool { + committeeIndexMatch := true + slotMatch := true + if rawCommitteeIndex != "" && data.CommitteeIndex != primitives.CommitteeIndex(committeeIndex) { + committeeIndexMatch = false + } + if rawSlot != "" && data.Slot != primitives.Slot(slot) { + slotMatch = false + } + return committeeIndexMatch && slotMatch } // SubmitAttestations submits an attestation object to node. If the attestation passes all validation diff --git a/beacon-chain/rpc/eth/beacon/handlers_pool_test.go b/beacon-chain/rpc/eth/beacon/handlers_pool_test.go index dae551ae2106..5c627acdd698 100644 --- a/beacon-chain/rpc/eth/beacon/handlers_pool_test.go +++ b/beacon-chain/rpc/eth/beacon/handlers_pool_test.go @@ -114,77 +114,352 @@ func TestListAttestations(t *testing.T) { }, Signature: bytesutil.PadTo([]byte("signature4"), 96), } - s := &Server{ - AttestationsPool: attestations.NewPool(), - } - require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{att1, att2})) - require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{att3, att4})) + t.Run("V1", func(t *testing.T) { + s := &Server{ + AttestationsPool: attestations.NewPool(), + } + require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{att1, att2})) + require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{att3, att4})) - t.Run("empty request", func(t *testing.T) { - url := "http://example.com" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + t.Run("empty request", func(t *testing.T) { + url := "http://example.com" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - s.ListAttestations(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.ListAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - assert.Equal(t, 4, len(resp.Data)) - }) - t.Run("slot request", func(t *testing.T) { - url := "http://example.com?slot=2" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s.ListAttestations(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) - s.ListAttestations(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.ListAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - assert.Equal(t, 2, len(resp.Data)) - for _, a := range resp.Data { - assert.Equal(t, "2", a.Data.Slot) - } - }) - t.Run("index request", func(t *testing.T) { - url := "http://example.com?committee_index=4" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 4, len(atts)) + }) + t.Run("slot request", func(t *testing.T) { + url := "http://example.com?slot=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - s.ListAttestations(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.ListAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - assert.Equal(t, 2, len(resp.Data)) - for _, a := range resp.Data { - assert.Equal(t, "4", a.Data.CommitteeIndex) - } - }) - t.Run("both slot + index request", func(t *testing.T) { - url := "http://example.com?slot=2&committee_index=4" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s.ListAttestations(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) - s.ListAttestations(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.ListAttestationsResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - assert.Equal(t, 1, len(resp.Data)) - for _, a := range resp.Data { - assert.Equal(t, "2", a.Data.Slot) - assert.Equal(t, "4", a.Data.CommitteeIndex) - } + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 2, len(atts)) + for _, a := range atts { + assert.Equal(t, "2", a.Data.Slot) + } + }) + t.Run("index request", func(t *testing.T) { + url := "http://example.com?committee_index=4" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestations(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 2, len(atts)) + for _, a := range atts { + assert.Equal(t, "4", a.Data.CommitteeIndex) + } + }) + t.Run("both slot + index request", func(t *testing.T) { + url := "http://example.com?slot=2&committee_index=4" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestations(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 1, len(atts)) + for _, a := range atts { + assert.Equal(t, "2", a.Data.Slot) + assert.Equal(t, "4", a.Data.CommitteeIndex) + } + }) + }) + t.Run("V2", func(t *testing.T) { + t.Run("Pre-Electra", func(t *testing.T) { + bs, err := util.NewBeaconState() + require.NoError(t, err) + s := &Server{ + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + AttestationsPool: attestations.NewPool(), + } + require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{att1, att2})) + require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{att3, att4})) + t.Run("empty request", func(t *testing.T) { + url := "http://example.com" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 4, len(atts)) + assert.Equal(t, "phase0", resp.Version) + }) + t.Run("slot request", func(t *testing.T) { + url := "http://example.com?slot=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 2, len(atts)) + assert.Equal(t, "phase0", resp.Version) + for _, a := range atts { + assert.Equal(t, "2", a.Data.Slot) + } + }) + t.Run("index request", func(t *testing.T) { + url := "http://example.com?committee_index=4" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 2, len(atts)) + assert.Equal(t, "phase0", resp.Version) + for _, a := range atts { + assert.Equal(t, "4", a.Data.CommitteeIndex) + } + }) + t.Run("both slot + index request", func(t *testing.T) { + url := "http://example.com?slot=2&committee_index=4" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 1, len(atts)) + assert.Equal(t, "phase0", resp.Version) + for _, a := range atts { + assert.Equal(t, "2", a.Data.Slot) + assert.Equal(t, "4", a.Data.CommitteeIndex) + } + }) + }) + t.Run("Post-Electra", func(t *testing.T) { + cb := primitives.NewAttestationCommitteeBits() + cb.SetBitAt(1, true) + attElectra1 := ðpbv1alpha1.AttestationElectra{ + AggregationBits: []byte{1, 10}, + Data: ðpbv1alpha1.AttestationData{ + Slot: 1, + CommitteeIndex: 1, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32), + Source: ðpbv1alpha1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot1"), 32), + }, + Target: ðpbv1alpha1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot1"), 32), + }, + }, + CommitteeBits: cb, + Signature: bytesutil.PadTo([]byte("signature1"), 96), + } + attElectra2 := ðpbv1alpha1.AttestationElectra{ + AggregationBits: []byte{1, 10}, + Data: ðpbv1alpha1.AttestationData{ + Slot: 1, + CommitteeIndex: 4, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32), + Source: ðpbv1alpha1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot2"), 32), + }, + Target: ðpbv1alpha1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot2"), 32), + }, + }, + CommitteeBits: cb, + Signature: bytesutil.PadTo([]byte("signature2"), 96), + } + attElectra3 := ðpbv1alpha1.AttestationElectra{ + AggregationBits: bitfield.NewBitlist(8), + Data: ðpbv1alpha1.AttestationData{ + Slot: 2, + CommitteeIndex: 2, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot3"), 32), + Source: ðpbv1alpha1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot3"), 32), + }, + Target: ðpbv1alpha1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot3"), 32), + }, + }, + CommitteeBits: cb, + Signature: bytesutil.PadTo([]byte("signature3"), 96), + } + attElectra4 := ðpbv1alpha1.AttestationElectra{ + AggregationBits: bitfield.NewBitlist(8), + Data: ðpbv1alpha1.AttestationData{ + Slot: 2, + CommitteeIndex: 4, + BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot4"), 32), + Source: ðpbv1alpha1.Checkpoint{ + Epoch: 1, + Root: bytesutil.PadTo([]byte("sourceroot4"), 32), + }, + Target: ðpbv1alpha1.Checkpoint{ + Epoch: 10, + Root: bytesutil.PadTo([]byte("targetroot4"), 32), + }, + }, + CommitteeBits: cb, + Signature: bytesutil.PadTo([]byte("signature4"), 96), + } + bs, err := util.NewBeaconStateElectra() + require.NoError(t, err) + s := &Server{ + AttestationsPool: attestations.NewPool(), + ChainInfoFetcher: &blockchainmock.ChainService{State: bs}, + } + require.NoError(t, s.AttestationsPool.SaveAggregatedAttestations([]ethpbv1alpha1.Att{attElectra1, attElectra2})) + require.NoError(t, s.AttestationsPool.SaveUnaggregatedAttestations([]ethpbv1alpha1.Att{attElectra3, attElectra4})) + + t.Run("empty request", func(t *testing.T) { + url := "http://example.com" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 4, len(atts)) + assert.Equal(t, "electra", resp.Version) + }) + t.Run("slot request", func(t *testing.T) { + url := "http://example.com?slot=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 2, len(atts)) + assert.Equal(t, "electra", resp.Version) + for _, a := range atts { + assert.Equal(t, "2", a.Data.Slot) + } + }) + t.Run("index request", func(t *testing.T) { + url := "http://example.com?committee_index=4" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 2, len(atts)) + assert.Equal(t, "electra", resp.Version) + for _, a := range atts { + assert.Equal(t, "4", a.Data.CommitteeIndex) + } + }) + t.Run("both slot + index request", func(t *testing.T) { + url := "http://example.com?slot=2&committee_index=4" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.ListAttestationsV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.ListAttestationsResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var atts []*structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &atts)) + assert.Equal(t, 1, len(atts)) + assert.Equal(t, "electra", resp.Version) + for _, a := range atts { + assert.Equal(t, "2", a.Data.Slot) + assert.Equal(t, "4", a.Data.CommitteeIndex) + } + }) + }) }) } diff --git a/config/params/config.go b/config/params/config.go index a1e59c1033a6..69a20bb6fec8 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -164,8 +164,8 @@ type BeaconChainConfig struct { CapellaForkEpoch primitives.Epoch `yaml:"CAPELLA_FORK_EPOCH" spec:"true"` // CapellaForkEpoch is used to represent the assigned fork epoch for capella. DenebForkVersion []byte `yaml:"DENEB_FORK_VERSION" spec:"true"` // DenebForkVersion is used to represent the fork version for deneb. DenebForkEpoch primitives.Epoch `yaml:"DENEB_FORK_EPOCH" spec:"true"` // DenebForkEpoch is used to represent the assigned fork epoch for deneb. - ElectraForkVersion []byte `yaml:"ELECTRA_FORK_VERSION" spec:"true"` // ElectraForkVersion is used to represent the fork version for deneb. - ElectraForkEpoch primitives.Epoch `yaml:"ELECTRA_FORK_EPOCH" spec:"true"` // ElectraForkEpoch is used to represent the assigned fork epoch for deneb. + ElectraForkVersion []byte `yaml:"ELECTRA_FORK_VERSION" spec:"true"` // ElectraForkVersion is used to represent the fork version for electra. + ElectraForkEpoch primitives.Epoch `yaml:"ELECTRA_FORK_EPOCH" spec:"true"` // ElectraForkEpoch is used to represent the assigned fork epoch for electra. ForkVersionSchedule map[[fieldparams.VersionLength]byte]primitives.Epoch // Schedule of fork epochs by version. ForkVersionNames map[[fieldparams.VersionLength]byte]string // Human-readable names of fork versions.