From 97aa3ecd142469d91364045c1c3e21150571892e Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 25 Sep 2024 16:53:22 +0200 Subject: [PATCH 01/12] add endpoint --- api/server/structs/endpoints_validator.go | 5 + beacon-chain/rpc/endpoints.go | 9 ++ beacon-chain/rpc/endpoints_test.go | 1 + beacon-chain/rpc/eth/validator/handlers.go | 98 ++++++++++++++----- .../rpc/eth/validator/handlers_test.go | 27 +++++ 5 files changed, 118 insertions(+), 22 deletions(-) diff --git a/api/server/structs/endpoints_validator.go b/api/server/structs/endpoints_validator.go index dfb94daea20a..de2a2685dc5a 100644 --- a/api/server/structs/endpoints_validator.go +++ b/api/server/structs/endpoints_validator.go @@ -10,6 +10,11 @@ type AggregateAttestationResponse struct { Data *Attestation `json:"data"` } +type AggregateAttestationV2Response struct { + Version string `json:"version"` + Data *Attestation `json:"data"` +} + type SubmitContributionAndProofsRequest struct { Data []*SignedContributionAndProof `json:"data"` } diff --git a/beacon-chain/rpc/endpoints.go b/beacon-chain/rpc/endpoints.go index 3d99b2d291ee..cdf25998dd58 100644 --- a/beacon-chain/rpc/endpoints.go +++ b/beacon-chain/rpc/endpoints.go @@ -199,6 +199,15 @@ func (s *Service) validatorEndpoints( handler: server.GetAggregateAttestation, methods: []string{http.MethodGet}, }, + { + template: "/eth/v2/validator/aggregate_attestation", + name: namespace + ".GetAggregateAttestationV2", + middleware: []middleware.Middleware{ + middleware.AcceptHeaderHandler([]string{api.JsonMediaType}), + }, + handler: server.GetAggregateAttestationV2, + methods: []string{http.MethodGet}, + }, { template: "/eth/v1/validator/contribution_and_proofs", name: namespace + ".SubmitContributionAndProofs", diff --git a/beacon-chain/rpc/endpoints_test.go b/beacon-chain/rpc/endpoints_test.go index 6b7799303f31..0a574ae935ff 100644 --- a/beacon-chain/rpc/endpoints_test.go +++ b/beacon-chain/rpc/endpoints_test.go @@ -98,6 +98,7 @@ func Test_endpoints(t *testing.T) { "/eth/v1/validator/blinded_blocks/{slot}": {http.MethodGet}, "/eth/v1/validator/attestation_data": {http.MethodGet}, "/eth/v1/validator/aggregate_attestation": {http.MethodGet}, + "/eth/v2/validator/aggregate_attestation": {http.MethodGet}, "/eth/v1/validator/aggregate_and_proofs": {http.MethodPost}, "/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost}, "/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost}, diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 597af22476a6..9787f9fdb565 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -31,6 +31,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" @@ -52,32 +53,51 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) return } - var match ethpbalpha.Att - var err error + match := s.aggregateAttestation(w, slot, 0, attDataRoot) + response := &structs.AggregateAttestationResponse{ + Data: &structs.Attestation{ + AggregationBits: hexutil.Encode(match.GetAggregationBits()), + Data: &structs.AttestationData{ + Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), + CommitteeIndex: strconv.FormatUint(uint64(match.GetData().CommitteeIndex), 10), + BeaconBlockRoot: hexutil.Encode(match.GetData().BeaconBlockRoot), + Source: &structs.Checkpoint{ + Epoch: strconv.FormatUint(uint64(match.GetData().Source.Epoch), 10), + Root: hexutil.Encode(match.GetData().Source.Root), + }, + Target: &structs.Checkpoint{ + Epoch: strconv.FormatUint(uint64(match.GetData().Target.Epoch), 10), + Root: hexutil.Encode(match.GetData().Target.Root), + }, + }, + Signature: hexutil.Encode(match.GetSignature()), + }} + httputil.WriteJson(w, response) +} - match, err = matchingAtt(s.AttestationsPool.AggregatedAttestations(), primitives.Slot(slot), attDataRoot) - if err != nil { - httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) +// GetAggregateAttestationV2 aggregates all attestations matching the given attestation data root and slot, returning the aggregated result. +func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Request) { + _, span := trace.StartSpan(r.Context(), "validator.GetAggregateAttestation") + defer span.End() + + _, attDataRoot, ok := shared.HexFromQuery(w, r, "attestation_data_root", fieldparams.RootLength, true) + if !ok { return } - if match == nil { - atts, err := s.AttestationsPool.UnaggregatedAttestations() - if err != nil { - httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError) - return - } - match, err = matchingAtt(atts, primitives.Slot(slot), attDataRoot) - if err != nil { - httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) - return - } + + _, slot, ok := shared.UintFromQuery(w, r, "slot", true) + if !ok { + return } - if match == nil { - httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) + + _, index, ok := shared.UintFromQuery(w, r, "committee_index", true) + if !ok { return } - response := &structs.AggregateAttestationResponse{ + match := s.aggregateAttestation(w, slot, index, attDataRoot) + response := &structs.AggregateAttestationV2Response{ + Version: version.String(match.Version()), Data: &structs.Attestation{ AggregationBits: hexutil.Encode(match.GetAggregationBits()), Data: &structs.AttestationData{ @@ -98,15 +118,49 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) httputil.WriteJson(w, response) } -func matchingAtt(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte) (ethpbalpha.Att, error) { +func (s *Server) aggregateAttestation(w http.ResponseWriter, slot, index uint64, attDataRoot []byte) ethpbalpha.Att { + var match ethpbalpha.Att + var err error + + match, err = matchingAtt(s.AttestationsPool.AggregatedAttestations(), primitives.Slot(slot), attDataRoot, primitives.CommitteeIndex(index)) + if err != nil { + httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) + return nil + } + if match == nil { + atts, err := s.AttestationsPool.UnaggregatedAttestations() + if err != nil { + httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError) + return nil + } + match, err = matchingAtt(atts, primitives.Slot(slot), attDataRoot, primitives.CommitteeIndex(index)) + if err != nil { + httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) + return nil + } + } + if match == nil { + httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) + return nil + } + return match +} + +func matchingAtt(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte, index primitives.CommitteeIndex) (ethpbalpha.Att, error) { for _, att := range atts { if att.GetData().Slot == slot { root, err := att.GetData().HashTreeRoot() if err != nil { return nil, errors.Wrap(err, "could not get attestation data root") } - if bytes.Equal(root[:], attDataRoot) { - return att, nil + if index == 0 { + if bytes.Equal(root[:], attDataRoot) { + return att, nil + } + } else { + if bytes.Equal(root[:], attDataRoot) && att.GetData().CommitteeIndex == index { + return att, nil + } } } } diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index 0aa758fe48b8..aa21089ed385 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -178,6 +178,33 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, "1", resp.Data.Data.Target.Epoch) assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.Target.Root) }) + t.Run("matching aggregated att V2", func(t *testing.T) { + reqRoot, err := attslot22.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=10" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.AggregateAttestationV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + assert.DeepEqual(t, "0x00010101", resp.Data.AggregationBits) + assert.DeepEqual(t, hexutil.Encode(sig22), resp.Data.Signature) + assert.Equal(t, "2", resp.Data.Data.Slot) + assert.Equal(t, "1", resp.Data.Data.CommitteeIndex) + assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.BeaconBlockRoot) + require.NotNil(t, resp.Data.Data.Source) + assert.Equal(t, "1", resp.Data.Data.Source.Epoch) + assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.Source.Root) + require.NotNil(t, resp.Data.Data.Target) + assert.Equal(t, "1", resp.Data.Data.Target.Epoch) + assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.Target.Root) + }) t.Run("matching unaggregated att", func(t *testing.T) { reqRoot, err := attslot32.Data.HashTreeRoot() require.NoError(t, err) From f9903c486734f96858e715dabf2866193ee80857 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 25 Sep 2024 17:03:39 +0200 Subject: [PATCH 02/12] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3fe9a5828d..16e64ef01c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Light client support: Implement capella and deneb changes. - Light client support: Implement `BlockToLightClientHeaderXXX` functions upto Deneb - GetBeaconStateV2: add Electra case. +- Added GetAggregatedAttestationsV2 endpoint. ### Changed From cbd5dd4a302b3a3f1ba87dfc7be0bd67654b8ccf Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 25 Sep 2024 17:43:31 +0200 Subject: [PATCH 03/12] fix tests --- beacon-chain/rpc/eth/validator/handlers.go | 6 + .../rpc/eth/validator/handlers_test.go | 114 +++++++++++++++++- 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 9787f9fdb565..65ab7ba0b9d1 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -54,6 +54,9 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) } match := s.aggregateAttestation(w, slot, 0, attDataRoot) + if match == nil { + return + } response := &structs.AggregateAttestationResponse{ Data: &structs.Attestation{ AggregationBits: hexutil.Encode(match.GetAggregationBits()), @@ -96,6 +99,9 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques } match := s.aggregateAttestation(w, slot, index, attDataRoot) + if match == nil { + return + } response := &structs.AggregateAttestationV2Response{ Version: version.String(match.Version()), Data: &structs.Attestation{ diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index aa21089ed385..d6887dc413be 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -182,7 +182,7 @@ func TestGetAggregateAttestation(t *testing.T) { reqRoot, err := attslot22.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=10" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=1" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -232,6 +232,33 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, "1", resp.Data.Data.Target.Epoch) assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.Target.Root) }) + t.Run("matching unaggregated att V2", func(t *testing.T) { + reqRoot, err := attslot32.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=1" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + resp := &structs.AggregateAttestationV2Response{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + assert.DeepEqual(t, "0x0001", resp.Data.AggregationBits) + assert.DeepEqual(t, hexutil.Encode(sig32), resp.Data.Signature) + assert.Equal(t, "3", resp.Data.Data.Slot) + assert.Equal(t, "1", resp.Data.Data.CommitteeIndex) + assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.BeaconBlockRoot) + require.NotNil(t, resp.Data.Data.Source) + assert.Equal(t, "1", resp.Data.Data.Source.Epoch) + assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.Source.Root) + require.NotNil(t, resp.Data.Data.Target) + assert.Equal(t, "1", resp.Data.Data.Target.Epoch) + assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.Target.Root) + }) t.Run("no matching attestation", func(t *testing.T) { attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" @@ -246,6 +273,23 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, http.StatusNotFound, e.Code) assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) }) + t.Run("no matching attestation V2", func(t *testing.T) { + //attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + reqRoot, err := attslot32.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusNotFound, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusNotFound, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) + }) t.Run("no attestation_data_root provided", func(t *testing.T) { url := "http://example.com?slot=2" request := httptest.NewRequest(http.MethodGet, url, nil) @@ -259,6 +303,19 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, e.Code) assert.Equal(t, true, strings.Contains(e.Message, "attestation_data_root is required")) }) + t.Run("no attestation_data_root provided V2", 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.GetAggregateAttestationV2(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, "attestation_data_root is required")) + }) t.Run("invalid attestation_data_root provided", func(t *testing.T) { url := "http://example.com?attestation_data_root=foo&slot=2" request := httptest.NewRequest(http.MethodGet, url, nil) @@ -272,6 +329,19 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, e.Code) assert.Equal(t, true, strings.Contains(e.Message, "attestation_data_root is invalid")) }) + t.Run("invalid attestation_data_root provided V2", func(t *testing.T) { + url := "http://example.com?attestation_data_root=foo&slot=2&committee_index=1" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "attestation_data_root is invalid")) + }) t.Run("no slot provided", func(t *testing.T) { attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) url := "http://example.com?attestation_data_root=" + attDataRoot @@ -286,6 +356,20 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, e.Code) assert.Equal(t, true, strings.Contains(e.Message, "slot is required")) }) + t.Run("no slot provided V2", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "slot is required")) + }) t.Run("invalid slot provided", func(t *testing.T) { attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=foo" @@ -300,6 +384,34 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, e.Code) assert.Equal(t, true, strings.Contains(e.Message, "slot is invalid")) }) + t.Run("invalid slot provided V2", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=foo" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "slot is invalid")) + }) + t.Run("invalid committee_index provided V2", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3&committee_index=foo" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "committee_index is invalid")) + }) } func TestGetAggregateAttestation_SameSlotAndRoot_ReturnMostAggregationBits(t *testing.T) { From 607cd31f320eb30ff9c6157c23f3c6235738f373 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Mon, 30 Sep 2024 15:50:58 +0200 Subject: [PATCH 04/12] fix endpoint --- api/server/structs/endpoints_validator.go | 4 +- beacon-chain/rpc/eth/validator/handlers.go | 30 +++++++-- .../rpc/eth/validator/handlers_test.go | 66 ++++++++++++------- 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/api/server/structs/endpoints_validator.go b/api/server/structs/endpoints_validator.go index de2a2685dc5a..d314d5cb7751 100644 --- a/api/server/structs/endpoints_validator.go +++ b/api/server/structs/endpoints_validator.go @@ -11,8 +11,8 @@ type AggregateAttestationResponse struct { } type AggregateAttestationV2Response struct { - Version string `json:"version"` - Data *Attestation `json:"data"` + Version string `json:"version"` + Data interface{} `json:"data"` } type SubmitContributionAndProofsRequest struct { diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 65ab7ba0b9d1..70618f7def0c 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -102,9 +102,30 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques if match == nil { return } - response := &structs.AggregateAttestationV2Response{ + resp := &structs.AggregateAttestationV2Response{ Version: version.String(match.Version()), - Data: &structs.Attestation{ + } + if match.Version() >= version.Electra { + resp.Data = &structs.AttestationElectra{ + AggregationBits: hexutil.Encode(match.GetAggregationBits()), + Data: &structs.AttestationData{ + Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), + CommitteeIndex: strconv.FormatUint(uint64(match.GetData().CommitteeIndex), 10), + BeaconBlockRoot: hexutil.Encode(match.GetData().BeaconBlockRoot), + Source: &structs.Checkpoint{ + Epoch: strconv.FormatUint(uint64(match.GetData().Source.Epoch), 10), + Root: hexutil.Encode(match.GetData().Source.Root), + }, + Target: &structs.Checkpoint{ + Epoch: strconv.FormatUint(uint64(match.GetData().Target.Epoch), 10), + Root: hexutil.Encode(match.GetData().Target.Root), + }, + }, + Signature: hexutil.Encode(match.GetSignature()), + CommitteeBits: hexutil.Encode(match.CommitteeBitsVal().Bytes()), + } + } else { + resp.Data = &structs.Attestation{ AggregationBits: hexutil.Encode(match.GetAggregationBits()), Data: &structs.AttestationData{ Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), @@ -120,8 +141,9 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques }, }, Signature: hexutil.Encode(match.GetSignature()), - }} - httputil.WriteJson(w, response) + } + } + httputil.WriteJson(w, resp) } func (s *Server) aggregateAttestation(w http.ResponseWriter, slot, index uint64, attDataRoot []byte) ethpbalpha.Att { diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index d6887dc413be..89ca38bc21e8 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -193,17 +193,28 @@ func TestGetAggregateAttestation(t *testing.T) { require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.NotNil(t, resp) require.NotNil(t, resp.Data) - assert.DeepEqual(t, "0x00010101", resp.Data.AggregationBits) - assert.DeepEqual(t, hexutil.Encode(sig22), resp.Data.Signature) - assert.Equal(t, "2", resp.Data.Data.Slot) - assert.Equal(t, "1", resp.Data.Data.CommitteeIndex) - assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.BeaconBlockRoot) - require.NotNil(t, resp.Data.Data.Source) - assert.Equal(t, "1", resp.Data.Data.Source.Epoch) - assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.Source.Root) - require.NotNil(t, resp.Data.Data.Target) - assert.Equal(t, "1", resp.Data.Data.Target.Epoch) - assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.Target.Root) + + dataMap, ok := resp.Data.(map[string]interface{}) + require.Equal(t, true, ok) + + assert.Equal(t, "0x00010101", dataMap["aggregation_bits"]) + assert.Equal(t, hexutil.Encode(sig22), dataMap["signature"]) + + attData, ok := dataMap["data"].(map[string]interface{}) + require.Equal(t, true, ok) + assert.Equal(t, "2", attData["slot"]) + assert.Equal(t, "1", attData["index"]) + assert.Equal(t, hexutil.Encode(root22), attData["beacon_block_root"]) + + sourceData, ok := attData["source"].(map[string]interface{}) + require.Equal(t, true, ok) + assert.Equal(t, "1", sourceData["epoch"]) + assert.Equal(t, hexutil.Encode(root22), sourceData["root"]) + + targetData, ok := attData["target"].(map[string]interface{}) + require.Equal(t, true, ok) + assert.Equal(t, "1", targetData["epoch"]) + assert.Equal(t, hexutil.Encode(root22), targetData["root"]) }) t.Run("matching unaggregated att", func(t *testing.T) { reqRoot, err := attslot32.Data.HashTreeRoot() @@ -247,17 +258,28 @@ func TestGetAggregateAttestation(t *testing.T) { require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.NotNil(t, resp) require.NotNil(t, resp.Data) - assert.DeepEqual(t, "0x0001", resp.Data.AggregationBits) - assert.DeepEqual(t, hexutil.Encode(sig32), resp.Data.Signature) - assert.Equal(t, "3", resp.Data.Data.Slot) - assert.Equal(t, "1", resp.Data.Data.CommitteeIndex) - assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.BeaconBlockRoot) - require.NotNil(t, resp.Data.Data.Source) - assert.Equal(t, "1", resp.Data.Data.Source.Epoch) - assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.Source.Root) - require.NotNil(t, resp.Data.Data.Target) - assert.Equal(t, "1", resp.Data.Data.Target.Epoch) - assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.Target.Root) + + dataMap, ok := resp.Data.(map[string]interface{}) + require.Equal(t, true, ok) + + assert.Equal(t, "0x0001", dataMap["aggregation_bits"]) + assert.Equal(t, hexutil.Encode(sig32), dataMap["signature"]) + + attData, ok := dataMap["data"].(map[string]interface{}) + require.Equal(t, true, ok) + assert.Equal(t, "3", attData["slot"]) + assert.Equal(t, "1", attData["index"]) + assert.Equal(t, hexutil.Encode(root32), attData["beacon_block_root"]) + + sourceData, ok := attData["source"].(map[string]interface{}) + require.Equal(t, true, ok) + assert.Equal(t, "1", sourceData["epoch"]) + assert.Equal(t, hexutil.Encode(root32), sourceData["root"]) + + targetData, ok := attData["target"].(map[string]interface{}) + require.Equal(t, true, ok) + assert.Equal(t, "1", targetData["epoch"]) + assert.Equal(t, hexutil.Encode(root32), targetData["root"]) }) t.Run("no matching attestation", func(t *testing.T) { attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) From f189e41eb5b1118a0adf6e4fc4bc4c5c4a7e33e1 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Tue, 1 Oct 2024 14:59:01 +0200 Subject: [PATCH 05/12] remove useless broken code --- beacon-chain/rpc/eth/validator/handlers.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 70618f7def0c..8ef40030bb2d 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -181,14 +181,8 @@ func matchingAtt(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte if err != nil { return nil, errors.Wrap(err, "could not get attestation data root") } - if index == 0 { - if bytes.Equal(root[:], attDataRoot) { - return att, nil - } - } else { - if bytes.Equal(root[:], attDataRoot) && att.GetData().CommitteeIndex == index { - return att, nil - } + if bytes.Equal(root[:], attDataRoot) && att.GetData().CommitteeIndex == index { + return att, nil } } } From cb6203818be39ac24d9dfb33b21b12c49cfb567d Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 9 Oct 2024 19:32:03 +0200 Subject: [PATCH 06/12] review + fix endpoint --- CHANGELOG.md | 2 +- api/server/structs/endpoints_validator.go | 8 +- beacon-chain/rpc/eth/validator/handlers.go | 145 +-- .../rpc/eth/validator/handlers_test.go | 889 ++++++++++-------- 4 files changed, 593 insertions(+), 451 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33eb660614f3..742c86102642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - GetBeaconStateV2: add Electra case. - Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875) - Tests to ensure sepolia config matches the official upstream yaml -- Added GetAggregatedAttestationsV2 endpoint. +- Added GetAggregatedAttestationV2 endpoint. ### Changed diff --git a/api/server/structs/endpoints_validator.go b/api/server/structs/endpoints_validator.go index d314d5cb7751..e87c8373347c 100644 --- a/api/server/structs/endpoints_validator.go +++ b/api/server/structs/endpoints_validator.go @@ -7,12 +7,8 @@ import ( ) type AggregateAttestationResponse struct { - Data *Attestation `json:"data"` -} - -type AggregateAttestationV2Response struct { - Version string `json:"version"` - Data interface{} `json:"data"` + Version string `json:"version,omitempty"` + Data json.RawMessage `json:"data"` } type SubmitContributionAndProofsRequest struct { diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 8ef40030bb2d..82d5a6d0371d 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -31,6 +31,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/proto/prysm/v1alpha1/attestation/aggregation/attestations" "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/sirupsen/logrus" @@ -53,104 +54,96 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) return } - match := s.aggregateAttestation(w, slot, 0, attDataRoot) + match := s.aggregateAttestation(w, primitives.Slot(slot), "", attDataRoot) if match == nil { return } - response := &structs.AggregateAttestationResponse{ - Data: &structs.Attestation{ - AggregationBits: hexutil.Encode(match.GetAggregationBits()), - Data: &structs.AttestationData{ - Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), - CommitteeIndex: strconv.FormatUint(uint64(match.GetData().CommitteeIndex), 10), - BeaconBlockRoot: hexutil.Encode(match.GetData().BeaconBlockRoot), - Source: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Source.Epoch), 10), - Root: hexutil.Encode(match.GetData().Source.Root), - }, - Target: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Target.Epoch), 10), - Root: hexutil.Encode(match.GetData().Target.Root), - }, + att := &structs.Attestation{ + AggregationBits: hexutil.Encode(match.GetAggregationBits()), + Data: &structs.AttestationData{ + Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), + CommitteeIndex: strconv.FormatUint(uint64(match.GetData().CommitteeIndex), 10), + BeaconBlockRoot: hexutil.Encode(match.GetData().BeaconBlockRoot), + Source: &structs.Checkpoint{ + Epoch: strconv.FormatUint(uint64(match.GetData().Source.Epoch), 10), + Root: hexutil.Encode(match.GetData().Source.Root), }, - Signature: hexutil.Encode(match.GetSignature()), - }} - httputil.WriteJson(w, response) + Target: &structs.Checkpoint{ + Epoch: strconv.FormatUint(uint64(match.GetData().Target.Epoch), 10), + Root: hexutil.Encode(match.GetData().Target.Root), + }, + }, + Signature: hexutil.Encode(match.GetSignature()), + } + + data, err := json.Marshal(att) + if err != nil { + httputil.HandleError(w, "Could not get marshal attestation data: "+err.Error(), http.StatusInternalServerError) + return + } + httputil.WriteJson(w, &structs.AggregateAttestationResponse{Data: data}) } // GetAggregateAttestationV2 aggregates all attestations matching the given attestation data root and slot, returning the aggregated result. func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Request) { - _, span := trace.StartSpan(r.Context(), "validator.GetAggregateAttestation") + _, span := trace.StartSpan(r.Context(), "validator.GetAggregateAttestationV2") defer span.End() _, attDataRoot, ok := shared.HexFromQuery(w, r, "attestation_data_root", fieldparams.RootLength, true) if !ok { return } - _, slot, ok := shared.UintFromQuery(w, r, "slot", true) if !ok { return } - _, index, ok := shared.UintFromQuery(w, r, "committee_index", true) if !ok { return } - - match := s.aggregateAttestation(w, slot, index, attDataRoot) + i := strconv.FormatUint(index, 10) + match := s.aggregateAttestation(w, primitives.Slot(slot), i, attDataRoot) if match == nil { return } - resp := &structs.AggregateAttestationV2Response{ + resp := &structs.AggregateAttestationResponse{ Version: version.String(match.Version()), } if match.Version() >= version.Electra { - resp.Data = &structs.AttestationElectra{ - AggregationBits: hexutil.Encode(match.GetAggregationBits()), - Data: &structs.AttestationData{ - Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), - CommitteeIndex: strconv.FormatUint(uint64(match.GetData().CommitteeIndex), 10), - BeaconBlockRoot: hexutil.Encode(match.GetData().BeaconBlockRoot), - Source: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Source.Epoch), 10), - Root: hexutil.Encode(match.GetData().Source.Root), - }, - Target: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Target.Epoch), 10), - Root: hexutil.Encode(match.GetData().Target.Root), - }, - }, - Signature: hexutil.Encode(match.GetSignature()), - CommitteeBits: hexutil.Encode(match.CommitteeBitsVal().Bytes()), + attPostElectra, ok := match.(*ethpbalpha.AttestationElectra) + if !ok { + httputil.HandleError(w, "Match is not of type AttestationElectra", http.StatusInternalServerError) + return } + att := structs.AttElectraFromConsensus(attPostElectra) + data, err := json.Marshal(att) + if err != nil { + httputil.HandleError(w, "Could not get marshal attestation data: "+err.Error(), http.StatusInternalServerError) + return + } + resp.Data = data } else { - resp.Data = &structs.Attestation{ - AggregationBits: hexutil.Encode(match.GetAggregationBits()), - Data: &structs.AttestationData{ - Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), - CommitteeIndex: strconv.FormatUint(uint64(match.GetData().CommitteeIndex), 10), - BeaconBlockRoot: hexutil.Encode(match.GetData().BeaconBlockRoot), - Source: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Source.Epoch), 10), - Root: hexutil.Encode(match.GetData().Source.Root), - }, - Target: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Target.Epoch), 10), - Root: hexutil.Encode(match.GetData().Target.Root), - }, - }, - Signature: hexutil.Encode(match.GetSignature()), + attPreElectra, ok := match.(*ethpbalpha.Attestation) + if !ok { + httputil.HandleError(w, "Match is not of type Attestation", http.StatusInternalServerError) + return } + att := structs.AttFromConsensus(attPreElectra) + data, err := json.Marshal(att) + if err != nil { + httputil.HandleError(w, "Could not get marshal attestation data: "+err.Error(), http.StatusInternalServerError) + return + } + resp.Data = data } httputil.WriteJson(w, resp) } -func (s *Server) aggregateAttestation(w http.ResponseWriter, slot, index uint64, attDataRoot []byte) ethpbalpha.Att { +func (s *Server) aggregateAttestation(w http.ResponseWriter, slot primitives.Slot, index string, attDataRoot []byte) ethpbalpha.Att { var match ethpbalpha.Att var err error - match, err = matchingAtt(s.AttestationsPool.AggregatedAttestations(), primitives.Slot(slot), attDataRoot, primitives.CommitteeIndex(index)) + match, err = matchingAtt(s.AttestationsPool.AggregatedAttestations(), slot, attDataRoot, index) if err != nil { httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) return nil @@ -161,28 +154,44 @@ func (s *Server) aggregateAttestation(w http.ResponseWriter, slot, index uint64, httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError) return nil } - match, err = matchingAtt(atts, primitives.Slot(slot), attDataRoot, primitives.CommitteeIndex(index)) + match, err = matchingAtt(atts, slot, attDataRoot, index) if err != nil { httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) return nil } - } - if match == nil { - httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) - return nil + if match == nil { + httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) + return nil + } + _, err = attestations.Aggregate([]ethpbalpha.Att{match}) + if err != nil { + httputil.HandleError(w, "Could not aggregate the matched unaggregated attestation: "+err.Error(), http.StatusInternalServerError) + return nil + } } return match } -func matchingAtt(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte, index primitives.CommitteeIndex) (ethpbalpha.Att, error) { +func matchingAtt(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte, index string) (ethpbalpha.Att, error) { for _, att := range atts { if att.GetData().Slot == slot { root, err := att.GetData().HashTreeRoot() if err != nil { return nil, errors.Wrap(err, "could not get attestation data root") } - if bytes.Equal(root[:], attDataRoot) && att.GetData().CommitteeIndex == index { - return att, nil + if index == "" { + if bytes.Equal(root[:], attDataRoot) { + return att, nil + } + } else { + i, err := strconv.ParseUint(index, 10, 64) + if err != nil { + return att, err + } + bits := att.CommitteeBitsVal().BitAt(i) + if bytes.Equal(root[:], attDataRoot) && bits { + return att, nil + } } } } diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index 89ca38bc21e8..8622bbc6eaa7 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/go-bitfield" "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" @@ -45,394 +46,527 @@ import ( ) func TestGetAggregateAttestation(t *testing.T) { - root1 := bytesutil.PadTo([]byte("root1"), 32) - sig1 := bytesutil.PadTo([]byte("sig1"), fieldparams.BLSSignatureLength) - attSlot1 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1}, - Data: ðpbalpha.AttestationData{ - Slot: 1, - CommitteeIndex: 1, - BeaconBlockRoot: root1, - Source: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root1, - }, - Target: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root1, - }, - }, - Signature: sig1, - } - root21 := bytesutil.PadTo([]byte("root2_1"), 32) - sig21 := bytesutil.PadTo([]byte("sig2_1"), fieldparams.BLSSignatureLength) - attslot21 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1, 1}, - Data: ðpbalpha.AttestationData{ - Slot: 2, - CommitteeIndex: 1, - BeaconBlockRoot: root21, - Source: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root21, - }, - Target: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root21, - }, - }, - Signature: sig21, - } - root22 := bytesutil.PadTo([]byte("root2_2"), 32) - sig22 := bytesutil.PadTo([]byte("sig2_2"), fieldparams.BLSSignatureLength) - attslot22 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1, 1, 1}, - Data: ðpbalpha.AttestationData{ - Slot: 2, - CommitteeIndex: 1, - BeaconBlockRoot: root22, - Source: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root22, - }, - Target: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root22, + t.Run("V1", func(t *testing.T) { + root1 := bytesutil.PadTo([]byte("root1"), 32) + sig1 := bytesutil.PadTo([]byte("sig1"), fieldparams.BLSSignatureLength) + attSlot1 := ðpbalpha.Attestation{ + AggregationBits: []byte{0, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 1, + CommitteeIndex: 1, + BeaconBlockRoot: root1, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root1, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root1, + }, }, - }, - Signature: sig22, - } - root31 := bytesutil.PadTo([]byte("root3_1"), 32) - sig31 := bls.NewAggregateSignature().Marshal() - attslot31 := ðpbalpha.Attestation{ - AggregationBits: []byte{1, 0}, - Data: ðpbalpha.AttestationData{ - Slot: 3, - CommitteeIndex: 1, - BeaconBlockRoot: root31, - Source: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root31, + Signature: sig1, + } + root21 := bytesutil.PadTo([]byte("root2_1"), 32) + sig21 := bytesutil.PadTo([]byte("sig2_1"), fieldparams.BLSSignatureLength) + attslot21 := ðpbalpha.Attestation{ + AggregationBits: []byte{0, 1, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 2, + CommitteeIndex: 1, + BeaconBlockRoot: root21, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root21, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root21, + }, }, - Target: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root31, + Signature: sig21, + } + root22 := bytesutil.PadTo([]byte("root2_2"), 32) + sig22 := bytesutil.PadTo([]byte("sig2_2"), fieldparams.BLSSignatureLength) + attslot22 := ðpbalpha.Attestation{ + AggregationBits: []byte{0, 1, 1, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 2, + CommitteeIndex: 1, + BeaconBlockRoot: root22, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root22, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root22, + }, }, - }, - Signature: sig31, - } - root32 := bytesutil.PadTo([]byte("root3_2"), 32) - sig32 := bls.NewAggregateSignature().Marshal() - attslot32 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1}, - Data: ðpbalpha.AttestationData{ - Slot: 3, - CommitteeIndex: 1, - BeaconBlockRoot: root32, - Source: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root32, + Signature: sig22, + } + root31 := bytesutil.PadTo([]byte("root3_1"), 32) + sig31 := bls.NewAggregateSignature().Marshal() + attslot31 := ðpbalpha.Attestation{ + AggregationBits: []byte{1, 0}, + Data: ðpbalpha.AttestationData{ + Slot: 3, + CommitteeIndex: 1, + BeaconBlockRoot: root31, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root31, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root31, + }, }, - Target: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root32, + Signature: sig31, + } + root32 := bytesutil.PadTo([]byte("root3_2"), 32) + sig32 := bls.NewAggregateSignature().Marshal() + attslot32 := ðpbalpha.Attestation{ + AggregationBits: []byte{0, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 3, + CommitteeIndex: 1, + BeaconBlockRoot: root32, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root32, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root32, + }, }, - }, - Signature: sig32, - } - - pool := attestations.NewPool() - err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attslot21, attslot22}) - assert.NoError(t, err) - err = pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attslot31, attslot32}) - assert.NoError(t, err) - - s := &Server{ - AttestationsPool: pool, - } - - t.Run("matching aggregated att", func(t *testing.T) { - reqRoot, err := attslot22.Data.HashTreeRoot() - require.NoError(t, err) - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetAggregateAttestation(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.AggregateAttestationResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - assert.DeepEqual(t, "0x00010101", resp.Data.AggregationBits) - assert.DeepEqual(t, hexutil.Encode(sig22), resp.Data.Signature) - assert.Equal(t, "2", resp.Data.Data.Slot) - assert.Equal(t, "1", resp.Data.Data.CommitteeIndex) - assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.BeaconBlockRoot) - require.NotNil(t, resp.Data.Data.Source) - assert.Equal(t, "1", resp.Data.Data.Source.Epoch) - assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.Source.Root) - require.NotNil(t, resp.Data.Data.Target) - assert.Equal(t, "1", resp.Data.Data.Target.Epoch) - assert.DeepEqual(t, hexutil.Encode(root22), resp.Data.Data.Target.Root) - }) - t.Run("matching aggregated att V2", func(t *testing.T) { - reqRoot, err := attslot22.Data.HashTreeRoot() - require.NoError(t, err) - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetAggregateAttestationV2(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.AggregateAttestationV2Response{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - - dataMap, ok := resp.Data.(map[string]interface{}) - require.Equal(t, true, ok) - - assert.Equal(t, "0x00010101", dataMap["aggregation_bits"]) - assert.Equal(t, hexutil.Encode(sig22), dataMap["signature"]) - - attData, ok := dataMap["data"].(map[string]interface{}) - require.Equal(t, true, ok) - assert.Equal(t, "2", attData["slot"]) - assert.Equal(t, "1", attData["index"]) - assert.Equal(t, hexutil.Encode(root22), attData["beacon_block_root"]) - - sourceData, ok := attData["source"].(map[string]interface{}) - require.Equal(t, true, ok) - assert.Equal(t, "1", sourceData["epoch"]) - assert.Equal(t, hexutil.Encode(root22), sourceData["root"]) - - targetData, ok := attData["target"].(map[string]interface{}) - require.Equal(t, true, ok) - assert.Equal(t, "1", targetData["epoch"]) - assert.Equal(t, hexutil.Encode(root22), targetData["root"]) - }) - t.Run("matching unaggregated att", func(t *testing.T) { - reqRoot, err := attslot32.Data.HashTreeRoot() - require.NoError(t, err) - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetAggregateAttestation(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.AggregateAttestationResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - assert.DeepEqual(t, "0x0001", resp.Data.AggregationBits) - assert.DeepEqual(t, hexutil.Encode(sig32), resp.Data.Signature) - assert.Equal(t, "3", resp.Data.Data.Slot) - assert.Equal(t, "1", resp.Data.Data.CommitteeIndex) - assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.BeaconBlockRoot) - require.NotNil(t, resp.Data.Data.Source) - assert.Equal(t, "1", resp.Data.Data.Source.Epoch) - assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.Source.Root) - require.NotNil(t, resp.Data.Data.Target) - assert.Equal(t, "1", resp.Data.Data.Target.Epoch) - assert.DeepEqual(t, hexutil.Encode(root32), resp.Data.Data.Target.Root) - }) - t.Run("matching unaggregated att V2", func(t *testing.T) { - reqRoot, err := attslot32.Data.HashTreeRoot() - require.NoError(t, err) - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetAggregateAttestationV2(writer, request) - assert.Equal(t, http.StatusOK, writer.Code) - resp := &structs.AggregateAttestationV2Response{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) - - dataMap, ok := resp.Data.(map[string]interface{}) - require.Equal(t, true, ok) - - assert.Equal(t, "0x0001", dataMap["aggregation_bits"]) - assert.Equal(t, hexutil.Encode(sig32), dataMap["signature"]) - - attData, ok := dataMap["data"].(map[string]interface{}) - require.Equal(t, true, ok) - assert.Equal(t, "3", attData["slot"]) - assert.Equal(t, "1", attData["index"]) - assert.Equal(t, hexutil.Encode(root32), attData["beacon_block_root"]) - - sourceData, ok := attData["source"].(map[string]interface{}) - require.Equal(t, true, ok) - assert.Equal(t, "1", sourceData["epoch"]) - assert.Equal(t, hexutil.Encode(root32), sourceData["root"]) - - targetData, ok := attData["target"].(map[string]interface{}) - require.Equal(t, true, ok) - assert.Equal(t, "1", targetData["epoch"]) - assert.Equal(t, hexutil.Encode(root32), targetData["root"]) - }) - t.Run("no matching attestation", func(t *testing.T) { - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetAggregateAttestation(writer, request) - assert.Equal(t, http.StatusNotFound, writer.Code) - e := &httputil.DefaultJsonError{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) - assert.Equal(t, http.StatusNotFound, e.Code) - assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) - }) - t.Run("no matching attestation V2", func(t *testing.T) { - //attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - reqRoot, err := attslot32.Data.HashTreeRoot() - require.NoError(t, err) - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=2" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} - - s.GetAggregateAttestationV2(writer, request) - assert.Equal(t, http.StatusNotFound, writer.Code) - e := &httputil.DefaultJsonError{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) - assert.Equal(t, http.StatusNotFound, e.Code) - assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) - }) - t.Run("no attestation_data_root provided", 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.GetAggregateAttestation(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, "attestation_data_root is required")) - }) - t.Run("no attestation_data_root provided V2", 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.GetAggregateAttestationV2(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, "attestation_data_root is required")) - }) - t.Run("invalid attestation_data_root provided", func(t *testing.T) { - url := "http://example.com?attestation_data_root=foo&slot=2" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + Signature: sig32, + } - s.GetAggregateAttestation(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, "attestation_data_root is invalid")) - }) - t.Run("invalid attestation_data_root provided V2", func(t *testing.T) { - url := "http://example.com?attestation_data_root=foo&slot=2&committee_index=1" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + pool := attestations.NewPool() + err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attslot21, attslot22}) + assert.NoError(t, err) + err = pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attslot31, attslot32}) + assert.NoError(t, err) - s.GetAggregateAttestationV2(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, "attestation_data_root is invalid")) - }) - t.Run("no slot provided", func(t *testing.T) { - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - url := "http://example.com?attestation_data_root=" + attDataRoot - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s := &Server{ + AttestationsPool: pool, + } + t.Run("matching aggregated att", func(t *testing.T) { + reqRoot, err := attslot22.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestation(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.AggregateAttestationResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + + assert.Equal(t, "0x00010101", attestation.AggregationBits) + assert.Equal(t, hexutil.Encode(sig22), attestation.Signature) + assert.Equal(t, "2", attestation.Data.Slot) + assert.Equal(t, "1", attestation.Data.CommitteeIndex) + assert.Equal(t, hexutil.Encode(root22), attestation.Data.BeaconBlockRoot) + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source) + assert.Equal(t, "1", attestation.Data.Source.Epoch) + assert.Equal(t, hexutil.Encode(root22), attestation.Data.Source.Root) + + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target) + assert.Equal(t, "1", attestation.Data.Target.Epoch) + assert.Equal(t, hexutil.Encode(root22), attestation.Data.Target.Root) + }) + t.Run("matching unaggregated att", func(t *testing.T) { + reqRoot, err := attslot32.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestation(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.AggregateAttestationResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + + assert.Equal(t, "0x0001", attestation.AggregationBits) + assert.Equal(t, hexutil.Encode(sig32), attestation.Signature) + assert.Equal(t, "3", attestation.Data.Slot) + assert.Equal(t, "1", attestation.Data.CommitteeIndex) + assert.Equal(t, hexutil.Encode(root32), attestation.Data.BeaconBlockRoot) + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source) + assert.Equal(t, "1", attestation.Data.Source.Epoch) + assert.Equal(t, hexutil.Encode(root32), attestation.Data.Source.Root) + + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target) + assert.Equal(t, "1", attestation.Data.Target.Epoch) + assert.Equal(t, hexutil.Encode(root32), attestation.Data.Target.Root) + }) + t.Run("no matching attestation", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} - s.GetAggregateAttestation(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, "slot is required")) - }) - t.Run("no slot provided V2", func(t *testing.T) { - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - url := "http://example.com?attestation_data_root=" + attDataRoot - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s.GetAggregateAttestation(writer, request) + assert.Equal(t, http.StatusNotFound, writer.Code) - s.GetAggregateAttestationV2(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, "slot is required")) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusNotFound, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) + }) + t.Run("no attestation_data_root provided", 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.GetAggregateAttestation(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, "attestation_data_root is required")) + }) + t.Run("invalid attestation_data_root provided", func(t *testing.T) { + url := "http://example.com?attestation_data_root=foo&slot=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestation(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, "attestation_data_root is invalid")) + }) + t.Run("no slot provided", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestation(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, "slot is required")) + }) + t.Run("invalid slot provided", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=foo" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestation(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, "slot is invalid")) + }) }) - t.Run("invalid slot provided", func(t *testing.T) { - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=foo" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + t.Run("V2", func(t *testing.T) { + committeeBits := bitfield.NewBitvector64() + root1 := bytesutil.PadTo([]byte("root1"), 32) + sig1 := bytesutil.PadTo([]byte("sig1"), fieldparams.BLSSignatureLength) + committeeBits.SetBitAt(1, true) + attSlot1 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 1, + CommitteeIndex: 1, + BeaconBlockRoot: root1, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root1, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root1, + }, + }, + Signature: sig1, + CommitteeBits: committeeBits, + } + root21 := bytesutil.PadTo([]byte("root2_1"), 32) + sig21 := bytesutil.PadTo([]byte("sig2_1"), fieldparams.BLSSignatureLength) + attslot21 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 1, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 2, + CommitteeIndex: 1, + BeaconBlockRoot: root21, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root21, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root21, + }, + }, + Signature: sig21, + CommitteeBits: committeeBits, + } + root22 := bytesutil.PadTo([]byte("root2_2"), 32) + sig22 := bytesutil.PadTo([]byte("sig2_2"), fieldparams.BLSSignatureLength) + attslot22 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 1, 1, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 2, + CommitteeIndex: 1, + BeaconBlockRoot: root22, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root22, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root22, + }, + }, + Signature: sig22, + CommitteeBits: committeeBits, + } + root31 := bytesutil.PadTo([]byte("root3_1"), 32) + sig31 := bls.NewAggregateSignature().Marshal() + attslot31 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{1, 0}, + Data: ðpbalpha.AttestationData{ + Slot: 3, + CommitteeIndex: 1, + BeaconBlockRoot: root31, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root31, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root31, + }, + }, + Signature: sig31, + CommitteeBits: committeeBits, + } + root32 := bytesutil.PadTo([]byte("root3_2"), 32) + sig32 := bls.NewAggregateSignature().Marshal() + attslot32 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 1}, + Data: ðpbalpha.AttestationData{ + Slot: 3, + CommitteeIndex: 1, + BeaconBlockRoot: root32, + Source: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root32, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: 1, + Root: root32, + }, + }, + Signature: sig32, + CommitteeBits: committeeBits, + } - s.GetAggregateAttestation(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, "slot is invalid")) - }) - t.Run("invalid slot provided V2", func(t *testing.T) { - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=foo" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + pool := attestations.NewPool() + err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attslot21, attslot22}) + assert.NoError(t, err) + err = pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attslot31, attslot32}) + assert.NoError(t, err) - s.GetAggregateAttestationV2(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, "slot is invalid")) - }) - t.Run("invalid committee_index provided V2", func(t *testing.T) { - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3&committee_index=foo" - request := httptest.NewRequest(http.MethodGet, url, nil) - writer := httptest.NewRecorder() - writer.Body = &bytes.Buffer{} + s := &Server{ + AttestationsPool: pool, + } + t.Run("matching aggregated att", func(t *testing.T) { + reqRoot, err := attslot22.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=1" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.AggregateAttestationResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var attestation structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + + assert.Equal(t, "0x00010101", attestation.AggregationBits) + assert.Equal(t, "0x0200000000000000", attestation.CommitteeBits) + assert.Equal(t, hexutil.Encode(sig22), attestation.Signature) + assert.Equal(t, "2", attestation.Data.Slot) + assert.Equal(t, "1", attestation.Data.CommitteeIndex) + assert.Equal(t, hexutil.Encode(root22), attestation.Data.BeaconBlockRoot) + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source) + assert.Equal(t, "1", attestation.Data.Source.Epoch) + assert.Equal(t, hexutil.Encode(root22), attestation.Data.Source.Root) + + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target) + assert.Equal(t, "1", attestation.Data.Target.Epoch) + assert.Equal(t, hexutil.Encode(root22), attestation.Data.Target.Root) + }) + t.Run("matching unaggregated att", func(t *testing.T) { + reqRoot, err := attslot32.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=1" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusOK, writer.Code) + + resp := &structs.AggregateAttestationResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) + + var attestation structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + + assert.Equal(t, "0x0001", attestation.AggregationBits) + assert.Equal(t, "0x0200000000000000", attestation.CommitteeBits) + assert.Equal(t, hexutil.Encode(sig32), attestation.Signature) + assert.Equal(t, "3", attestation.Data.Slot) + assert.Equal(t, "1", attestation.Data.CommitteeIndex) + assert.Equal(t, hexutil.Encode(root32), attestation.Data.BeaconBlockRoot) + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source) + assert.Equal(t, "1", attestation.Data.Source.Epoch) + assert.Equal(t, hexutil.Encode(root32), attestation.Data.Source.Root) + + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target) + assert.Equal(t, "1", attestation.Data.Target.Epoch) + assert.Equal(t, hexutil.Encode(root32), attestation.Data.Target.Root) + }) + t.Run("no matching attestation", func(t *testing.T) { + //attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + reqRoot, err := attslot32.Data.HashTreeRoot() + require.NoError(t, err) + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=2" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(writer, request) + assert.Equal(t, http.StatusNotFound, writer.Code) + e := &httputil.DefaultJsonError{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) + assert.Equal(t, http.StatusNotFound, e.Code) + assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) + }) + t.Run("no attestation_data_root provided", 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.GetAggregateAttestationV2(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, "attestation_data_root is required")) + }) - s.GetAggregateAttestationV2(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, "committee_index is invalid")) + t.Run("invalid attestation_data_root provided", func(t *testing.T) { + url := "http://example.com?attestation_data_root=foo&slot=2&committee_index=1" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "attestation_data_root is invalid")) + }) + t.Run("no slot provided", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "slot is required")) + }) + t.Run("invalid slot provided", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=foo" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "slot is invalid")) + }) + t.Run("invalid committee_index provided", func(t *testing.T) { + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3&committee_index=foo" + request := httptest.NewRequest(http.MethodGet, url, nil) + writer := httptest.NewRecorder() + writer.Body = &bytes.Buffer{} + + s.GetAggregateAttestationV2(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, "committee_index is invalid")) + }) }) } @@ -492,7 +626,10 @@ func TestGetAggregateAttestation_SameSlotAndRoot_ReturnMostAggregationBits(t *te resp := &structs.AggregateAttestationResponse{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) require.NotNil(t, resp) - assert.DeepEqual(t, "0x03000001", resp.Data.AggregationBits) + + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + assert.DeepEqual(t, "0x03000001", attestation.AggregationBits) } func TestSubmitContributionAndProofs(t *testing.T) { From f94403f05afc12db0c04c40ccf9c5e4080dd6ff0 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 9 Oct 2024 19:35:52 +0200 Subject: [PATCH 07/12] gaz --- beacon-chain/rpc/eth/validator/BUILD.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beacon-chain/rpc/eth/validator/BUILD.bazel b/beacon-chain/rpc/eth/validator/BUILD.bazel index dc941ae72578..fbeef46ceb49 100644 --- a/beacon-chain/rpc/eth/validator/BUILD.bazel +++ b/beacon-chain/rpc/eth/validator/BUILD.bazel @@ -40,6 +40,7 @@ go_library( "//monitoring/tracing/trace:go_default_library", "//network/httputil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//proto/prysm/v1alpha1/attestation/aggregation/attestations:go_default_library", "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", @@ -92,6 +93,7 @@ go_test( "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@org_uber_go_mock//gomock:go_default_library", ], From 8f8ef6705586b7f7024b63738db5936365f67513 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 9 Oct 2024 19:55:17 +0200 Subject: [PATCH 08/12] fix aggregate selection proof test --- .../client/beacon-api/submit_aggregate_selection_proof.go | 8 +++++++- .../beacon-api/submit_aggregate_selection_proof_test.go | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/validator/client/beacon-api/submit_aggregate_selection_proof.go b/validator/client/beacon-api/submit_aggregate_selection_proof.go index 3aeda87136d6..be5e368835c9 100644 --- a/validator/client/beacon-api/submit_aggregate_selection_proof.go +++ b/validator/client/beacon-api/submit_aggregate_selection_proof.go @@ -2,6 +2,7 @@ package beacon_api import ( "context" + "encoding/json" "net/url" "strconv" @@ -52,7 +53,12 @@ func (c *beaconApiValidatorClient) submitAggregateSelectionProof( return nil, err } - aggregatedAttestation, err := convertAttestationToProto(aggregateAttestationResponse.Data) + var attData *ethpb.Attestation // Replace with your appropriate struct + if err := json.Unmarshal(aggregateAttestationResponse.Data, &attData); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal aggregate attestation data") + } + + aggregatedAttestation, err := convertAttestationToProto(jsonifyAttestation(attData)) if err != nil { return nil, errors.Wrap(err, "failed to convert aggregate attestation json to proto") } diff --git a/validator/client/beacon-api/submit_aggregate_selection_proof_test.go b/validator/client/beacon-api/submit_aggregate_selection_proof_test.go index f8cdcb1111d9..fed7b7558620 100644 --- a/validator/client/beacon-api/submit_aggregate_selection_proof_test.go +++ b/validator/client/beacon-api/submit_aggregate_selection_proof_test.go @@ -2,6 +2,7 @@ package beacon_api import ( "context" + "encoding/json" "errors" "fmt" "testing" @@ -124,6 +125,9 @@ func TestSubmitAggregateSelectionProof(t *testing.T) { test.attestationDataErr, ).Times(test.attestationDataCalled) + attestationJSON, err := json.Marshal(aggregateAttestation) + require.NoError(t, err) + // Call attestation data to get attestation data root to query aggregate attestation. jsonRestHandler.EXPECT().Get( gomock.Any(), @@ -132,7 +136,7 @@ func TestSubmitAggregateSelectionProof(t *testing.T) { ).SetArg( 2, structs.AggregateAttestationResponse{ - Data: jsonifyAttestation(aggregateAttestation), + Data: attestationJSON, }, ).Return( test.aggregateAttestationErr, From 99558f4c7aecd6e4c32333799941370896fa7759 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Thu, 10 Oct 2024 14:08:55 +0200 Subject: [PATCH 09/12] fixes --- beacon-chain/rpc/eth/validator/handlers.go | 29 ++++++------------- .../submit_aggregate_selection_proof.go | 4 +-- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 82d5a6d0371d..b9be5573386d 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -58,27 +58,16 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) if match == nil { return } - att := &structs.Attestation{ - AggregationBits: hexutil.Encode(match.GetAggregationBits()), - Data: &structs.AttestationData{ - Slot: strconv.FormatUint(uint64(match.GetData().Slot), 10), - CommitteeIndex: strconv.FormatUint(uint64(match.GetData().CommitteeIndex), 10), - BeaconBlockRoot: hexutil.Encode(match.GetData().BeaconBlockRoot), - Source: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Source.Epoch), 10), - Root: hexutil.Encode(match.GetData().Source.Root), - }, - Target: &structs.Checkpoint{ - Epoch: strconv.FormatUint(uint64(match.GetData().Target.Epoch), 10), - Root: hexutil.Encode(match.GetData().Target.Root), - }, - }, - Signature: hexutil.Encode(match.GetSignature()), - } + matchedAtt, ok := match.(*ethpbalpha.Attestation) + if !ok { + httputil.HandleError(w, "Match is not of type Attestation", http.StatusInternalServerError) + return + } + att := structs.AttFromConsensus(matchedAtt) data, err := json.Marshal(att) if err != nil { - httputil.HandleError(w, "Could not get marshal attestation data: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) return } httputil.WriteJson(w, &structs.AggregateAttestationResponse{Data: data}) @@ -118,7 +107,7 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques att := structs.AttElectraFromConsensus(attPostElectra) data, err := json.Marshal(att) if err != nil { - httputil.HandleError(w, "Could not get marshal attestation data: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) return } resp.Data = data @@ -131,7 +120,7 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques att := structs.AttFromConsensus(attPreElectra) data, err := json.Marshal(att) if err != nil { - httputil.HandleError(w, "Could not get marshal attestation data: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) return } resp.Data = data diff --git a/validator/client/beacon-api/submit_aggregate_selection_proof.go b/validator/client/beacon-api/submit_aggregate_selection_proof.go index be5e368835c9..1d7269f0277f 100644 --- a/validator/client/beacon-api/submit_aggregate_selection_proof.go +++ b/validator/client/beacon-api/submit_aggregate_selection_proof.go @@ -53,12 +53,12 @@ func (c *beaconApiValidatorClient) submitAggregateSelectionProof( return nil, err } - var attData *ethpb.Attestation // Replace with your appropriate struct + var attData *structs.Attestation if err := json.Unmarshal(aggregateAttestationResponse.Data, &attData); err != nil { return nil, errors.Wrap(err, "failed to unmarshal aggregate attestation data") } - aggregatedAttestation, err := convertAttestationToProto(jsonifyAttestation(attData)) + aggregatedAttestation, err := convertAttestationToProto(attData) if err != nil { return nil, errors.Wrap(err, "failed to convert aggregate attestation json to proto") } From 3a4acbdd3d508189f38c1efddb2e55226fe3d81d Mon Sep 17 00:00:00 2001 From: rkapka Date: Tue, 15 Oct 2024 17:48:08 +0200 Subject: [PATCH 10/12] new way of aggregating --- beacon-chain/rpc/eth/validator/handlers.go | 142 ++++++++++++--------- 1 file changed, 80 insertions(+), 62 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index b9be5573386d..27c04f9e6f07 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -2,11 +2,13 @@ package validator import ( "bytes" + "cmp" "context" "encoding/json" "fmt" "io" "net/http" + "slices" "sort" "strconv" "time" @@ -48,24 +50,21 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) if !ok { return } - _, slot, ok := shared.UintFromQuery(w, r, "slot", true) if !ok { return } - match := s.aggregateAttestation(w, primitives.Slot(slot), "", attDataRoot) - if match == nil { + agg := s.aggregatedAttestation(w, primitives.Slot(slot), attDataRoot, 0) + if agg == nil { return } - - matchedAtt, ok := match.(*ethpbalpha.Attestation) + typedAgg, ok := agg.(*ethpbalpha.Attestation) if !ok { - httputil.HandleError(w, "Match is not of type Attestation", http.StatusInternalServerError) + httputil.HandleError(w, fmt.Sprintf("Attestation is not of type %T", ðpbalpha.Attestation{}), http.StatusInternalServerError) return } - att := structs.AttFromConsensus(matchedAtt) - data, err := json.Marshal(att) + data, err := json.Marshal(structs.AttFromConsensus(typedAgg)) if err != nil { httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) return @@ -90,35 +89,33 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques if !ok { return } - i := strconv.FormatUint(index, 10) - match := s.aggregateAttestation(w, primitives.Slot(slot), i, attDataRoot) - if match == nil { + + agg := s.aggregatedAttestation(w, primitives.Slot(slot), attDataRoot, primitives.CommitteeIndex(index)) + if agg == nil { return } resp := &structs.AggregateAttestationResponse{ - Version: version.String(match.Version()), + Version: version.String(agg.Version()), } - if match.Version() >= version.Electra { - attPostElectra, ok := match.(*ethpbalpha.AttestationElectra) + if agg.Version() >= version.Electra { + typedAgg, ok := agg.(*ethpbalpha.AttestationElectra) if !ok { - httputil.HandleError(w, "Match is not of type AttestationElectra", http.StatusInternalServerError) + httputil.HandleError(w, fmt.Sprintf("Attestation is not of type %T", ðpbalpha.AttestationElectra{}), http.StatusInternalServerError) return } - att := structs.AttElectraFromConsensus(attPostElectra) - data, err := json.Marshal(att) + data, err := json.Marshal(structs.AttElectraFromConsensus(typedAgg)) if err != nil { httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) return } resp.Data = data } else { - attPreElectra, ok := match.(*ethpbalpha.Attestation) + typedAgg, ok := agg.(*ethpbalpha.Attestation) if !ok { - httputil.HandleError(w, "Match is not of type Attestation", http.StatusInternalServerError) + httputil.HandleError(w, fmt.Sprintf("Attestation is not of type %T", ðpbalpha.Attestation{}), http.StatusInternalServerError) return } - att := structs.AttFromConsensus(attPreElectra) - data, err := json.Marshal(att) + data, err := json.Marshal(structs.AttFromConsensus(typedAgg)) if err != nil { httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) return @@ -128,63 +125,84 @@ func (s *Server) GetAggregateAttestationV2(w http.ResponseWriter, r *http.Reques httputil.WriteJson(w, resp) } -func (s *Server) aggregateAttestation(w http.ResponseWriter, slot primitives.Slot, index string, attDataRoot []byte) ethpbalpha.Att { - var match ethpbalpha.Att +func (s *Server) aggregatedAttestation(w http.ResponseWriter, slot primitives.Slot, attDataRoot []byte, index primitives.CommitteeIndex) ethpbalpha.Att { var err error - match, err = matchingAtt(s.AttestationsPool.AggregatedAttestations(), slot, attDataRoot, index) + match, err := matchingAtts(s.AttestationsPool.AggregatedAttestations(), slot, attDataRoot, index) + if err != nil { + httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) + return nil + } + if len(match) > 0 { + // If there are multiple matching aggregated attestations, + // then we return the one with the most aggregation bits. + slices.SortFunc(match, func(a, b ethpbalpha.Att) int { + return cmp.Compare(a.GetAggregationBits().Count(), b.GetAggregationBits().Count()) + }) + return match[0] + } + + atts, err := s.AttestationsPool.UnaggregatedAttestations() + if err != nil { + httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError) + return nil + } + match, err = matchingAtts(atts, slot, attDataRoot, index) if err != nil { httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) return nil } if match == nil { - atts, err := s.AttestationsPool.UnaggregatedAttestations() - if err != nil { - httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError) - return nil - } - match, err = matchingAtt(atts, slot, attDataRoot, index) - if err != nil { - httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) - return nil - } - if match == nil { - httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) - return nil - } - _, err = attestations.Aggregate([]ethpbalpha.Att{match}) - if err != nil { - httputil.HandleError(w, "Could not aggregate the matched unaggregated attestation: "+err.Error(), http.StatusInternalServerError) - return nil - } + httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) + return nil } - return match + agg, err := attestations.Aggregate(match) + if err != nil { + httputil.HandleError(w, "Could not aggregate unaggregated attestation: "+err.Error(), http.StatusInternalServerError) + return nil + } + + // Aggregating unaggregated attestations will in theory always return just one aggregate, + // so we can take the first one and be done with it. + return agg[0] } -func matchingAtt(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte, index string) (ethpbalpha.Att, error) { +func matchingAtts(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte, index primitives.CommitteeIndex) ([]ethpbalpha.Att, error) { + if len(atts) == 0 { + return []ethpbalpha.Att{}, nil + } + + postElectra := atts[0].Version() >= version.Electra + + result := make([]ethpbalpha.Att, 0) for _, att := range atts { - if att.GetData().Slot == slot { - root, err := att.GetData().HashTreeRoot() + if att.GetData().Slot != slot { + continue + } + // We ignore the committee index from the request before Electra. + // This is because before Electra the committee index is part of the attestation data, + // meaning that comparing the data root is sufficient. + // Post-Electra the committee index in the data root is always 0, so we need to + // compare the committee index separately. + if postElectra { + ci, err := att.GetCommitteeIndex() if err != nil { - return nil, errors.Wrap(err, "could not get attestation data root") + return nil, err } - if index == "" { - if bytes.Equal(root[:], attDataRoot) { - return att, nil - } - } else { - i, err := strconv.ParseUint(index, 10, 64) - if err != nil { - return att, err - } - bits := att.CommitteeBitsVal().BitAt(i) - if bytes.Equal(root[:], attDataRoot) && bits { - return att, nil - } + if ci != index { + continue } } + root, err := att.GetData().HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not get attestation data root") + } + if bytes.Equal(root[:], attDataRoot) { + result = append(result, att) + } } - return nil, nil + + return result, nil } // SubmitContributionAndProofs publishes multiple signed sync committee contribution and proofs. From f1a1591b0b6090e24914ccd22158a19db2892359 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 16 Oct 2024 11:01:56 +0200 Subject: [PATCH 11/12] nit --- beacon-chain/rpc/eth/validator/handlers.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 27c04f9e6f07..17b8d02c994d 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -130,7 +130,7 @@ func (s *Server) aggregatedAttestation(w http.ResponseWriter, slot primitives.Sl match, err := matchingAtts(s.AttestationsPool.AggregatedAttestations(), slot, attDataRoot, index) if err != nil { - httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not get matching attestations: "+err.Error(), http.StatusInternalServerError) return nil } if len(match) > 0 { @@ -149,16 +149,16 @@ func (s *Server) aggregatedAttestation(w http.ResponseWriter, slot primitives.Sl } match, err = matchingAtts(atts, slot, attDataRoot, index) if err != nil { - httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not get matching attestations: "+err.Error(), http.StatusInternalServerError) return nil } if match == nil { - httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) + httputil.HandleError(w, "No matching attestations found", http.StatusNotFound) return nil } agg, err := attestations.Aggregate(match) if err != nil { - httputil.HandleError(w, "Could not aggregate unaggregated attestation: "+err.Error(), http.StatusInternalServerError) + httputil.HandleError(w, "Could not aggregate unaggregated attestations: "+err.Error(), http.StatusInternalServerError) return nil } From 55f78d96c1cc163aa41a5e5ad882aac7282ae712 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 16 Oct 2024 11:55:52 +0200 Subject: [PATCH 12/12] fix part of the tests --- beacon-chain/rpc/eth/validator/handlers.go | 2 +- beacon-chain/rpc/eth/validator/handlers_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 17b8d02c994d..c1dcb9504a0c 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -152,7 +152,7 @@ func (s *Server) aggregatedAttestation(w http.ResponseWriter, slot primitives.Sl httputil.HandleError(w, "Could not get matching attestations: "+err.Error(), http.StatusInternalServerError) return nil } - if match == nil { + if len(match) == 0 { httputil.HandleError(w, "No matching attestations found", http.StatusNotFound) return nil } diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index 8622bbc6eaa7..cb268a831438 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -237,7 +237,7 @@ func TestGetAggregateAttestation(t *testing.T) { e := &httputil.DefaultJsonError{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) assert.Equal(t, http.StatusNotFound, e.Code) - assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) + assert.Equal(t, true, strings.Contains(e.Message, "No matching attestations found")) }) t.Run("no attestation_data_root provided", func(t *testing.T) { url := "http://example.com?slot=2" @@ -411,7 +411,7 @@ func TestGetAggregateAttestation(t *testing.T) { reqRoot, err := attslot22.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=1" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=0" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -448,7 +448,7 @@ func TestGetAggregateAttestation(t *testing.T) { reqRoot, err := attslot32.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=1" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=0" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -486,7 +486,7 @@ func TestGetAggregateAttestation(t *testing.T) { reqRoot, err := attslot32.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=2" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=0" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{} @@ -496,7 +496,7 @@ func TestGetAggregateAttestation(t *testing.T) { e := &httputil.DefaultJsonError{} require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e)) assert.Equal(t, http.StatusNotFound, e.Code) - assert.Equal(t, true, strings.Contains(e.Message, "No matching attestation found")) + assert.Equal(t, true, strings.Contains(e.Message, "No matching attestations found")) }) t.Run("no attestation_data_root provided", func(t *testing.T) { url := "http://example.com?slot=2" @@ -513,7 +513,7 @@ func TestGetAggregateAttestation(t *testing.T) { }) t.Run("invalid attestation_data_root provided", func(t *testing.T) { - url := "http://example.com?attestation_data_root=foo&slot=2&committee_index=1" + url := "http://example.com?attestation_data_root=foo&slot=2&committee_index=0" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() writer.Body = &bytes.Buffer{}