From 97aa3ecd142469d91364045c1c3e21150571892e Mon Sep 17 00:00:00 2001 From: Saolyn Date: Wed, 25 Sep 2024 16:53:22 +0200 Subject: [PATCH 01/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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{} From 29fc4f77a51e794d1a408d42fde3ccf42e100343 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Thu, 17 Oct 2024 17:03:14 +0200 Subject: [PATCH 13/18] fix tests --- .../rpc/eth/validator/handlers_test.go | 551 +++++++++--------- 1 file changed, 271 insertions(+), 280 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index cb268a831438..a40fbe6cbf7c 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -46,114 +46,55 @@ import ( ) func TestGetAggregateAttestation(t *testing.T) { + root1 := bytesutil.PadTo([]byte("root1"), 32) + sig1 := bytesutil.PadTo([]byte("sig1"), fieldparams.BLSSignatureLength) + root21 := bytesutil.PadTo([]byte("root2_1"), 32) + sig21 := bytesutil.PadTo([]byte("sig2_1"), fieldparams.BLSSignatureLength) + root22 := bytesutil.PadTo([]byte("root2_2"), 32) + sig22 := bytesutil.PadTo([]byte("sig2_2"), fieldparams.BLSSignatureLength) + root31 := bytesutil.PadTo([]byte("root3_1"), 32) + sig31 := bytesutil.PadTo([]byte("sig3_1"), fieldparams.BLSSignatureLength) + root32 := bytesutil.PadTo([]byte("root3_2"), 32) + sig32 := bls.NewAggregateSignature().Marshal() + 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: sig1, + AggregationBits: []byte{0, 1, 1}, + Data: createAttestationData(1, 1, 1, root1), + Signature: sig1, } - root21 := bytesutil.PadTo([]byte("root2_1"), 32) - sig21 := bytesutil.PadTo([]byte("sig2_1"), fieldparams.BLSSignatureLength) - attslot21 := ðpbalpha.Attestation{ + 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, + Data: createAttestationData(2, 1, 1, root21), + Signature: sig21, } - root22 := bytesutil.PadTo([]byte("root2_2"), 32) - sig22 := bytesutil.PadTo([]byte("sig2_2"), fieldparams.BLSSignatureLength) - attslot22 := ðpbalpha.Attestation{ + 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: sig22, + Data: createAttestationData(2, 1, 1, root22), + Signature: sig22, } - root31 := bytesutil.PadTo([]byte("root3_1"), 32) - sig31 := bls.NewAggregateSignature().Marshal() - attslot31 := ðpbalpha.Attestation{ + 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, - }, - }, - Signature: sig31, + Data: createAttestationData(3, 1, 1, root31), + Signature: sig31, } - root32 := bytesutil.PadTo([]byte("root3_2"), 32) - sig32 := bls.NewAggregateSignature().Marshal() - attslot32 := ðpbalpha.Attestation{ + 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, + Data: createAttestationData(3, 1, 1, root32), + Signature: sig32, } pool := attestations.NewPool() - err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attslot21, attslot22}) + err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attSlot21, attSlot22}) assert.NoError(t, err) - err = pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attslot31, attslot32}) + 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() + reqRoot, err := attSlot22.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" @@ -189,7 +130,7 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, hexutil.Encode(root22), attestation.Data.Target.Root) }) t.Run("matching unaggregated att", func(t *testing.T) { - reqRoot, err := attslot32.Data.HashTreeRoot() + reqRoot, err := attSlot32.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" @@ -294,124 +235,92 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, true, strings.Contains(e.Message, "slot is invalid")) }) }) + + //we should have test cases for: + //0 matching atts (to test the no-panic scenario) + //1 matching aggregated att + //>1 matching aggregated att takes the one with most bit count + //1 matching unaggregated att + //>1 matching unaggregated att results in these atts being aggregated (so eg you have one att with bits 0001 and one with bits 1000 -> you get an att with bits 1001) 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, - } + //committeeBits := bitfield.NewBitvector64() + //committeeBits.SetBitAt(1, true) + //attSlot1 := ðpbalpha.AttestationElectra{ + // AggregationBits: []byte{0, 1, 1}, + // Data: createAttestationData(1, 0, 1, root1), + // CommitteeBits: committeeBits, + // Signature: sig1, + //} + ////committeeBits.SetBitAt(1, true) + //attSlot21 := ðpbalpha.AttestationElectra{ + // AggregationBits: []byte{0, 1, 1}, + // Data: createAttestationData(2, 0, 1, root21), + // CommitteeBits: committeeBits, + // Signature: sig21, + //} + ////committeeBits.SetBitAt(2, true) + //attSlot22 := ðpbalpha.AttestationElectra{ + // AggregationBits: []byte{0, 1, 1, 1}, + // Data: createAttestationData(2, 0, 1, root22), + // CommitteeBits: committeeBits, + // Signature: sig22, + //} + ////committeeBits.SetBitAt(2, true) + //attSlot31 := ðpbalpha.AttestationElectra{ + // AggregationBits: []byte{1, 0}, + // Data: createAttestationData(3, 0, 1, root31), + // CommitteeBits: committeeBits, + // Signature: sig31, + //} + ////committeeBits.SetBitAt(3, true) + //attSlot32 := ðpbalpha.AttestationElectra{ + // AggregationBits: []byte{0, 1}, + // Data: createAttestationData(3, 0, 1, root32), + // CommitteeBits: committeeBits, + // 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) + t.Run("no matching attestation", func(t *testing.T) { + s := &Server{ + AttestationsPool: attestations.NewPool(), + } - s := &Server{ - AttestationsPool: pool, - } - t.Run("matching aggregated att", func(t *testing.T) { - reqRoot, err := attslot22.Data.HashTreeRoot() + attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + 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.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 attestations found")) + }) + t.Run("1 matching aggregated att", func(t *testing.T) { + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(1, true) + attSlot22 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 1, 1, 1}, + Data: createAttestationData(2, 0, 1, root22), + CommitteeBits: committeeBits, + Signature: sig22, + } + + pool := attestations.NewPool() + err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot22}) + assert.NoError(t, err) + + s := &Server{ + AttestationsPool: pool, + } + + 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=0" + 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{} @@ -431,7 +340,7 @@ func TestGetAggregateAttestation(t *testing.T) { 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, "0", attestation.Data.CommitteeIndex) assert.Equal(t, hexutil.Encode(root22), attestation.Data.BeaconBlockRoot) // Source checkpoint checks @@ -444,11 +353,41 @@ func TestGetAggregateAttestation(t *testing.T) { 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() + t.Run("more than 1 matching aggregated att", func(t *testing.T) { + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(1, true) + + attSlot22v1 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 1, 1}, + Data: createAttestationData(2, 0, 1, root22), + CommitteeBits: committeeBits, + Signature: sig22, + } + attSlot22v2 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{1, 1, 1}, + Data: createAttestationData(2, 0, 1, root22), + CommitteeBits: committeeBits, + Signature: sig22, + } + attSlot22v3 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{1, 1, 1, 1}, // Most bits set + Data: createAttestationData(2, 0, 1, root22), + CommitteeBits: committeeBits, + Signature: sig22, + } + + pool := attestations.NewPool() + err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot22v1, attSlot22v2, attSlot22v3}) + assert.NoError(t, err) + + s := &Server{ + AttestationsPool: pool, + } + + reqRoot, err := attSlot22v1.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=0" + 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{} @@ -464,112 +403,164 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.AttestationElectra require.NoError(t, json.Unmarshal(resp.Data, &attestation)) - assert.Equal(t, "0x0001", attestation.AggregationBits) + // Ensure that the attestation with the most aggregation bits is returned (attSlot22v3) + assert.Equal(t, "0x010101", 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) + assert.Equal(t, hexutil.Encode(sig22), attestation.Signature) + assert.Equal(t, "2", attestation.Data.Slot) + assert.Equal(t, "0", 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(root32), attestation.Data.Source.Root) + 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(root32), attestation.Data.Target.Root) + assert.Equal(t, hexutil.Encode(root22), 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() + t.Run("1 matching unaggregated attestation", func(t *testing.T) { + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(1, true) + + unaggAtt := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 1, 1}, + Data: createAttestationData(2, 0, 1, root22), + CommitteeBits: committeeBits, + Signature: sig22, + } + + pool := attestations.NewPool() + err := pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggAtt}) + assert.NoError(t, err) + + s := &Server{ + AttestationsPool: pool, + } + + reqRoot, err := unaggAtt.Data.HashTreeRoot() require.NoError(t, err) attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=0" + 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.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 attestations 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{} + assert.Equal(t, http.StatusOK, 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, "attestation_data_root is required")) - }) + resp := &structs.AggregateAttestationResponse{} + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) + require.NotNil(t, resp) + require.NotNil(t, resp.Data) - t.Run("invalid attestation_data_root provided", func(t *testing.T) { - 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{} + var attestation structs.AttestationElectra + require.NoError(t, json.Unmarshal(resp.Data, &attestation)) - 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{} + assert.Equal(t, "0x000101", 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, "0", attestation.Data.CommitteeIndex) + assert.Equal(t, hexutil.Encode(root22), attestation.Data.BeaconBlockRoot) - 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{} + // 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) - 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")) + // 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("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" + t.Run("multiple matching unaggregated attestations", func(t *testing.T) { + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(1, true) + + unaggAtt1 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{0, 0, 0, 1}, + Data: createAttestationData(2, 0, 1, root22), + CommitteeBits: committeeBits, + Signature: sig22, + } + unaggAtt2 := ðpbalpha.AttestationElectra{ + AggregationBits: []byte{1, 0, 0, 1}, + Data: createAttestationData(2, 0, 1, root22), + CommitteeBits: committeeBits, + Signature: sig22, + } + + pool := attestations.NewPool() + err := pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggAtt1, unaggAtt2}) + assert.NoError(t, err) + + s := &Server{ + AttestationsPool: pool, + } + + reqRoot, err := unaggAtt1.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.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")) + 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, "0x01000001", 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, "0", 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) }) }) } +func createAttestationData( + slot primitives.Slot, + committeeIndex primitives.CommitteeIndex, + epoch primitives.Epoch, + root []byte, +) *ethpbalpha.AttestationData { + return ðpbalpha.AttestationData{ + Slot: slot, + CommitteeIndex: committeeIndex, + BeaconBlockRoot: root, + Source: ðpbalpha.Checkpoint{ + Epoch: epoch, + Root: root, + }, + Target: ðpbalpha.Checkpoint{ + Epoch: epoch, + Root: root, + }, + } +} + func TestGetAggregateAttestation_SameSlotAndRoot_ReturnMostAggregationBits(t *testing.T) { root := bytesutil.PadTo([]byte("root"), 32) sig := bytesutil.PadTo([]byte("sig"), fieldparams.BLSSignatureLength) From b980b91b19484922eca9dbae03ed4b4118ae4e50 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Thu, 17 Oct 2024 17:07:04 +0200 Subject: [PATCH 14/18] cleanup --- .../rpc/eth/validator/handlers_test.go | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index a40fbe6cbf7c..70966a778f25 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -235,51 +235,7 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, true, strings.Contains(e.Message, "slot is invalid")) }) }) - - //we should have test cases for: - //0 matching atts (to test the no-panic scenario) - //1 matching aggregated att - //>1 matching aggregated att takes the one with most bit count - //1 matching unaggregated att - //>1 matching unaggregated att results in these atts being aggregated (so eg you have one att with bits 0001 and one with bits 1000 -> you get an att with bits 1001) t.Run("V2", func(t *testing.T) { - //committeeBits := bitfield.NewBitvector64() - //committeeBits.SetBitAt(1, true) - //attSlot1 := ðpbalpha.AttestationElectra{ - // AggregationBits: []byte{0, 1, 1}, - // Data: createAttestationData(1, 0, 1, root1), - // CommitteeBits: committeeBits, - // Signature: sig1, - //} - ////committeeBits.SetBitAt(1, true) - //attSlot21 := ðpbalpha.AttestationElectra{ - // AggregationBits: []byte{0, 1, 1}, - // Data: createAttestationData(2, 0, 1, root21), - // CommitteeBits: committeeBits, - // Signature: sig21, - //} - ////committeeBits.SetBitAt(2, true) - //attSlot22 := ðpbalpha.AttestationElectra{ - // AggregationBits: []byte{0, 1, 1, 1}, - // Data: createAttestationData(2, 0, 1, root22), - // CommitteeBits: committeeBits, - // Signature: sig22, - //} - ////committeeBits.SetBitAt(2, true) - //attSlot31 := ðpbalpha.AttestationElectra{ - // AggregationBits: []byte{1, 0}, - // Data: createAttestationData(3, 0, 1, root31), - // CommitteeBits: committeeBits, - // Signature: sig31, - //} - ////committeeBits.SetBitAt(3, true) - //attSlot32 := ðpbalpha.AttestationElectra{ - // AggregationBits: []byte{0, 1}, - // Data: createAttestationData(3, 0, 1, root32), - // CommitteeBits: committeeBits, - // Signature: sig32, - //} - t.Run("no matching attestation", func(t *testing.T) { s := &Server{ AttestationsPool: attestations.NewPool(), From 8f88cb4139e9dcd1a28dc0ddea9167282bd9122d Mon Sep 17 00:00:00 2001 From: rkapka Date: Fri, 18 Oct 2024 12:15:05 +0200 Subject: [PATCH 15/18] fix AggSelectionProof test --- .../client/beacon-api/submit_aggregate_selection_proof_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fed7b7558620..6fa1abebed41 100644 --- a/validator/client/beacon-api/submit_aggregate_selection_proof_test.go +++ b/validator/client/beacon-api/submit_aggregate_selection_proof_test.go @@ -125,7 +125,7 @@ func TestSubmitAggregateSelectionProof(t *testing.T) { test.attestationDataErr, ).Times(test.attestationDataCalled) - attestationJSON, err := json.Marshal(aggregateAttestation) + attestationJSON, err := json.Marshal(jsonifyAttestation(aggregateAttestation)) require.NoError(t, err) // Call attestation data to get attestation data root to query aggregate attestation. From a358698ab44d7acf62f96e471e7c6232b7965614 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Thu, 31 Oct 2024 13:41:25 +0100 Subject: [PATCH 16/18] tests --- .../rpc/eth/validator/handlers_test.go | 653 +++++++----------- 1 file changed, 241 insertions(+), 412 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index 70966a778f25..d8e0e7aefbdf 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -47,451 +47,342 @@ import ( func TestGetAggregateAttestation(t *testing.T) { root1 := bytesutil.PadTo([]byte("root1"), 32) - sig1 := bytesutil.PadTo([]byte("sig1"), fieldparams.BLSSignatureLength) - root21 := bytesutil.PadTo([]byte("root2_1"), 32) - sig21 := bytesutil.PadTo([]byte("sig2_1"), fieldparams.BLSSignatureLength) - root22 := bytesutil.PadTo([]byte("root2_2"), 32) - sig22 := bytesutil.PadTo([]byte("sig2_2"), fieldparams.BLSSignatureLength) - root31 := bytesutil.PadTo([]byte("root3_1"), 32) - sig31 := bytesutil.PadTo([]byte("sig3_1"), fieldparams.BLSSignatureLength) - root32 := bytesutil.PadTo([]byte("root3_2"), 32) - sig32 := bls.NewAggregateSignature().Marshal() + sig1 := bytesutil.PadTo([]byte("sig1"), 96) + root2 := bytesutil.PadTo([]byte("root2"), 32) + sig2 := bytesutil.PadTo([]byte("sig2"), 96) + root3 := bytesutil.PadTo([]byte("root3"), 32) + sig3 := bytesutil.PadTo([]byte("sig3"), 96) t.Run("V1", func(t *testing.T) { - attSlot1 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1, 1}, - Data: createAttestationData(1, 1, 1, root1), - Signature: sig1, - } - attSlot21 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1, 1}, - Data: createAttestationData(2, 1, 1, root21), - Signature: sig21, - } - attSlot22 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1, 1, 1}, - Data: createAttestationData(2, 1, 1, root22), - Signature: sig22, - } - attSlot31 := ðpbalpha.Attestation{ - AggregationBits: []byte{1, 0}, - Data: createAttestationData(3, 1, 1, root31), - Signature: sig31, + createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte, sig []byte) *ethpbalpha.Attestation { + return ðpbalpha.Attestation{ + AggregationBits: aggregationBits, + Data: createAttestationData(slot, 1, 1, root), + Signature: sig, + } } - attSlot32 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 1}, - Data: createAttestationData(3, 1, 1, root32), - Signature: sig32, + + attSlot1 := createAttestation(1, bitfield.Bitlist{0b1101}, root1, sig1) + attSlot2 := createAttestation(2, bitfield.Bitlist{0b1110}, root1, sig1) + attSlot3 := createAttestation(3, bitfield.Bitlist{0b1011}, root1, sig1) + attSlot4 := createAttestation(4, bitfield.Bitlist{0b10010}, root2, sig2) + attslot42 := createAttestation(4, bitfield.Bitlist{0b10001}, root2, sig2) + attSlot5 := createAttestation(5, bitfield.Bitlist{0b1000}, root3, sig3) + + compareResult := func( + t *testing.T, + attestation structs.Attestation, + expectedSlot string, + expectedAggregationBits string, + expectedRoot []byte, + expectedSig []byte, + ) { + assert.Equal(t, expectedAggregationBits, attestation.AggregationBits, "Unexpected aggregation bits in attestation") + assert.Equal(t, hexutil.Encode(expectedSig), attestation.Signature, "Signature mismatch") + assert.Equal(t, expectedSlot, attestation.Data.Slot, "Slot mismatch in attestation data") + assert.Equal(t, "1", attestation.Data.CommitteeIndex, "Committee index mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.BeaconBlockRoot, "Beacon block root mismatch") + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source, "Source checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Source.Epoch, "Source epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Source.Root, "Source root mismatch") + + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target, "Target checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Target.Epoch, "Target epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Target.Root, "Target root mismatch") } 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) + t.Run("non-matching attestation request", func(t *testing.T) { + // Test case where no matching attestation exists. + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 1, len(agg), "Expected 3 aggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" 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) + assert.Equal(t, http.StatusNotFound, writer.Code, "Expected HTTP status NotFound for non-matching request") }) - t.Run("matching unaggregated att", func(t *testing.T) { - reqRoot, err := attSlot32.Data.HashTreeRoot() - require.NoError(t, err) + t.Run("1 matching aggregated attestation", func(t *testing.T) { + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 1, len(agg), "Expected 1 aggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + 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) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - resp := &structs.AggregateAttestationResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") var attestation structs.Attestation - require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - 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) + compareResult(t, attestation, "2", "0x0e", root1, sig1) }) - t.Run("no matching attestation", func(t *testing.T) { - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) + t.Run("multiple matching aggregated attestations", func(t *testing.T) { + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attSlot2, attSlot3}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 3, len(agg), "Expected 3 aggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + 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.StatusNotFound, writer.Code) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - 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 attestations 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{} + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") - 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{} + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - 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")) + compareResult(t, attestation, "2", "0x0e", root1, sig1) }) - 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 + t.Run("1 matching unaggregated attestation", func(t *testing.T) { + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() + require.NoError(t, err) + require.Equal(t, 1, len(unagg), "Expected 1 unaggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot4.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" 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")) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + compareResult(t, attestation, "4", "0x12", root2, sig2) }) - 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" + t.Run("only 2 matching unaggregated attestations", func(t *testing.T) { + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4, attslot42, attSlot5}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() + require.NoError(t, err) + require.Equal(t, 3, len(unagg), "Expected 3 unaggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot4.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" 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")) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") + + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") + + var attestation structs.Attestation + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + compareResult(t, attestation, "4", "0x12", root2, sig2) }) }) t.Run("V2", func(t *testing.T) { - t.Run("no matching attestation", func(t *testing.T) { - s := &Server{ - AttestationsPool: attestations.NewPool(), + createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte, sig []byte, bits uint64) *ethpbalpha.AttestationElectra { + committeeBits := bitfield.NewBitvector64() + committeeBits.SetBitAt(bits, true) + + return ðpbalpha.AttestationElectra{ + CommitteeBits: committeeBits, + AggregationBits: aggregationBits, + Data: createAttestationData(slot, 1, 1, root), + Signature: sig, } + } + + attSlot1 := createAttestation(1, bitfield.Bitlist{0b1101}, root1, sig1, 1) + attSlot2 := createAttestation(2, bitfield.Bitlist{0b1110}, root1, sig1, 1) + attSlot3 := createAttestation(3, bitfield.Bitlist{0b1011}, root1, sig1, 1) + attSlot4 := createAttestation(4, bitfield.Bitlist{0b10010}, root2, sig2, 2) + attslot42 := createAttestation(4, bitfield.Bitlist{0b10001}, root2, sig2, 2) + attSlot5 := createAttestation(5, bitfield.Bitlist{0b1000}, root3, sig3, 3) + + compareResult := func( + t *testing.T, + attestation structs.AttestationElectra, + expectedSlot string, + expectedAggregationBits string, + expectedRoot []byte, + expectedSig []byte, + expectedBits string, + ) { + assert.Equal(t, expectedAggregationBits, attestation.AggregationBits, "Unexpected aggregation bits in attestation") + assert.Equal(t, expectedBits, attestation.CommitteeBits) + assert.Equal(t, hexutil.Encode(expectedSig), attestation.Signature, "Signature mismatch") + assert.Equal(t, expectedSlot, attestation.Data.Slot, "Slot mismatch in attestation data") + assert.Equal(t, "1", attestation.Data.CommitteeIndex, "Committee index mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.BeaconBlockRoot, "Beacon block root mismatch") + + // Source checkpoint checks + require.NotNil(t, attestation.Data.Source, "Source checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Source.Epoch, "Source epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Source.Root, "Source root mismatch") - attDataRoot := hexutil.Encode(bytesutil.PadTo([]byte("foo"), 32)) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=1" + // Target checkpoint checks + require.NotNil(t, attestation.Data.Target, "Target checkpoint should not be nil") + assert.Equal(t, "1", attestation.Data.Target.Epoch, "Target epoch mismatch") + assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.Target.Root, "Target root mismatch") + } + + pool := attestations.NewPool() + s := &Server{ + AttestationsPool: pool, + } + t.Run("non-matching attestation request", func(t *testing.T) { + // Test case where no matching attestation exists. + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 1, len(agg), "Expected 3 aggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") + attDataRoot := hexutil.Encode(reqRoot[:]) + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&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.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 attestations found")) + assert.Equal(t, http.StatusNotFound, writer.Code, "Expected HTTP status NotFound for non-matching request") }) - t.Run("1 matching aggregated att", func(t *testing.T) { - committeeBits := bitfield.NewBitvector64() - committeeBits.SetBitAt(1, true) - attSlot22 := ðpbalpha.AttestationElectra{ - AggregationBits: []byte{0, 1, 1, 1}, - Data: createAttestationData(2, 0, 1, root22), - CommitteeBits: committeeBits, - Signature: sig22, - } - - pool := attestations.NewPool() - err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot22}) - assert.NoError(t, err) - - s := &Server{ - AttestationsPool: pool, - } - - reqRoot, err := attSlot22.Data.HashTreeRoot() - require.NoError(t, err) + t.Run("1 matching aggregated attestation", func(t *testing.T) { + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 1, len(agg), "Expected 1 aggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") 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) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - resp := &structs.AggregateAttestationResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - 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, "0", 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) + compareResult(t, attestation, "2", "0x0e", root1, sig1, "1") }) - t.Run("more than 1 matching aggregated att", func(t *testing.T) { - committeeBits := bitfield.NewBitvector64() - committeeBits.SetBitAt(1, true) - - attSlot22v1 := ðpbalpha.AttestationElectra{ - AggregationBits: []byte{0, 1, 1}, - Data: createAttestationData(2, 0, 1, root22), - CommitteeBits: committeeBits, - Signature: sig22, - } - attSlot22v2 := ðpbalpha.AttestationElectra{ - AggregationBits: []byte{1, 1, 1}, - Data: createAttestationData(2, 0, 1, root22), - CommitteeBits: committeeBits, - Signature: sig22, - } - attSlot22v3 := ðpbalpha.AttestationElectra{ - AggregationBits: []byte{1, 1, 1, 1}, // Most bits set - Data: createAttestationData(2, 0, 1, root22), - CommitteeBits: committeeBits, - Signature: sig22, - } - - pool := attestations.NewPool() - err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot22v1, attSlot22v2, attSlot22v3}) - assert.NoError(t, err) - - s := &Server{ - AttestationsPool: pool, - } - - reqRoot, err := attSlot22v1.Data.HashTreeRoot() - require.NoError(t, err) + t.Run("multiple matching aggregated attestations", func(t *testing.T) { + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attSlot2, attSlot3}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 3, len(agg), "Expected 3 aggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot2.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") 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) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - resp := &structs.AggregateAttestationResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation)) + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - // Ensure that the attestation with the most aggregation bits is returned (attSlot22v3) - assert.Equal(t, "0x010101", 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, "0", 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) + compareResult(t, attestation, "2", "0x0e", root1, sig1, "1") }) t.Run("1 matching unaggregated attestation", func(t *testing.T) { - committeeBits := bitfield.NewBitvector64() - committeeBits.SetBitAt(1, true) - - unaggAtt := ðpbalpha.AttestationElectra{ - AggregationBits: []byte{0, 1, 1}, - Data: createAttestationData(2, 0, 1, root22), - CommitteeBits: committeeBits, - Signature: sig22, - } - - pool := attestations.NewPool() - err := pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggAtt}) - assert.NoError(t, err) - - s := &Server{ - AttestationsPool: pool, - } - - reqRoot, err := unaggAtt.Data.HashTreeRoot() + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() require.NoError(t, err) + require.Equal(t, 1, len(unagg), "Expected 1 unaggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot4.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") 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=4" + "&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) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - resp := &structs.AggregateAttestationResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation)) - - assert.Equal(t, "0x000101", 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, "0", 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) + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + compareResult(t, attestation, "4", "0x12", root2, sig2, "1") }) - t.Run("multiple matching unaggregated attestations", func(t *testing.T) { - committeeBits := bitfield.NewBitvector64() - committeeBits.SetBitAt(1, true) - - unaggAtt1 := ðpbalpha.AttestationElectra{ - AggregationBits: []byte{0, 0, 0, 1}, - Data: createAttestationData(2, 0, 1, root22), - CommitteeBits: committeeBits, - Signature: sig22, - } - unaggAtt2 := ðpbalpha.AttestationElectra{ - AggregationBits: []byte{1, 0, 0, 1}, - Data: createAttestationData(2, 0, 1, root22), - CommitteeBits: committeeBits, - Signature: sig22, - } - - pool := attestations.NewPool() - err := pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggAtt1, unaggAtt2}) - assert.NoError(t, err) - - s := &Server{ - AttestationsPool: pool, - } - - reqRoot, err := unaggAtt1.Data.HashTreeRoot() + t.Run("only 2 matching unaggregated attestations", func(t *testing.T) { + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4, attslot42, attSlot5}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() require.NoError(t, err) + require.Equal(t, 3, len(unagg), "Expected 3 unaggregated attestations") + s.AttestationsPool = pool + + reqRoot, err := attSlot4.Data.HashTreeRoot() + require.NoError(t, err, "Failed to generate attestation data hash tree root") 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=4" + "&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) + require.Equal(t, http.StatusOK, writer.Code, "Expected HTTP status OK") - resp := &structs.AggregateAttestationResponse{} - require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp)) - require.NotNil(t, resp) - require.NotNil(t, resp.Data) + var resp structs.AggregateAttestationResponse + require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp), "Failed to unmarshal response") + require.NotNil(t, resp.Data, "Response data should not be nil") var attestation structs.AttestationElectra - require.NoError(t, json.Unmarshal(resp.Data, &attestation)) - - assert.Equal(t, "0x01000001", 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, "0", 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) + require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") + compareResult(t, attestation, "4", "0x12", root2, sig2, "1") }) }) } @@ -517,68 +408,6 @@ func createAttestationData( } } -func TestGetAggregateAttestation_SameSlotAndRoot_ReturnMostAggregationBits(t *testing.T) { - root := bytesutil.PadTo([]byte("root"), 32) - sig := bytesutil.PadTo([]byte("sig"), fieldparams.BLSSignatureLength) - att1 := ðpbalpha.Attestation{ - AggregationBits: []byte{3, 0, 0, 1}, - Data: ðpbalpha.AttestationData{ - Slot: 1, - CommitteeIndex: 1, - BeaconBlockRoot: root, - Source: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root, - }, - Target: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root, - }, - }, - Signature: sig, - } - att2 := ðpbalpha.Attestation{ - AggregationBits: []byte{0, 3, 0, 1}, - Data: ðpbalpha.AttestationData{ - Slot: 1, - CommitteeIndex: 1, - BeaconBlockRoot: root, - Source: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root, - }, - Target: ðpbalpha.Checkpoint{ - Epoch: 1, - Root: root, - }, - }, - Signature: sig, - } - pool := attestations.NewPool() - err := pool.SaveAggregatedAttestations([]ethpbalpha.Att{att1, att2}) - assert.NoError(t, err) - s := &Server{ - AttestationsPool: pool, - } - reqRoot, err := att1.Data.HashTreeRoot() - require.NoError(t, err) - attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" - 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) - - var attestation structs.Attestation - require.NoError(t, json.Unmarshal(resp.Data, &attestation)) - assert.DeepEqual(t, "0x03000001", attestation.AggregationBits) -} - func TestSubmitContributionAndProofs(t *testing.T) { c := &core.Service{ OperationNotifier: (&mockChain.ChainService{}).OperationNotifier(), From 3ee2e549ddf372da65746a04eee41cf4bbc3795f Mon Sep 17 00:00:00 2001 From: rkapka Date: Tue, 5 Nov 2024 19:19:12 +0700 Subject: [PATCH 17/18] v1 tests --- beacon-chain/rpc/eth/validator/BUILD.bazel | 1 + beacon-chain/rpc/eth/validator/handlers.go | 2 +- .../rpc/eth/validator/handlers_test.go | 97 ++++++++----------- 3 files changed, 44 insertions(+), 56 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/BUILD.bazel b/beacon-chain/rpc/eth/validator/BUILD.bazel index fbeef46ceb49..8ff0e1bb0001 100644 --- a/beacon-chain/rpc/eth/validator/BUILD.bazel +++ b/beacon-chain/rpc/eth/validator/BUILD.bazel @@ -83,6 +83,7 @@ go_test( "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", "//crypto/bls:go_default_library", + "//crypto/bls/common:go_default_library", "//encoding/bytesutil:go_default_library", "//network/httputil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index c1dcb9504a0c..086f422632fc 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -137,7 +137,7 @@ func (s *Server) aggregatedAttestation(w http.ResponseWriter, slot primitives.Sl // 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 cmp.Compare(b.GetAggregationBits().Count(), a.GetAggregationBits().Count()) }) return match[0] } diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index d8e0e7aefbdf..0a3a10d57bdd 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -35,6 +35,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/crypto/bls" + "github.com/prysmaticlabs/prysm/v5/crypto/bls/common" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v5/network/httputil" ethpbalpha "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -47,27 +48,28 @@ import ( func TestGetAggregateAttestation(t *testing.T) { root1 := bytesutil.PadTo([]byte("root1"), 32) - sig1 := bytesutil.PadTo([]byte("sig1"), 96) root2 := bytesutil.PadTo([]byte("root2"), 32) - sig2 := bytesutil.PadTo([]byte("sig2"), 96) - root3 := bytesutil.PadTo([]byte("root3"), 32) - sig3 := bytesutil.PadTo([]byte("sig3"), 96) + key, err := bls.RandKey() + require.NoError(t, err) + sig := key.Sign([]byte("sig")) t.Run("V1", func(t *testing.T) { - createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte, sig []byte) *ethpbalpha.Attestation { + createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte) *ethpbalpha.Attestation { return ðpbalpha.Attestation{ AggregationBits: aggregationBits, Data: createAttestationData(slot, 1, 1, root), - Signature: sig, + Signature: sig.Marshal(), } } - attSlot1 := createAttestation(1, bitfield.Bitlist{0b1101}, root1, sig1) - attSlot2 := createAttestation(2, bitfield.Bitlist{0b1110}, root1, sig1) - attSlot3 := createAttestation(3, bitfield.Bitlist{0b1011}, root1, sig1) - attSlot4 := createAttestation(4, bitfield.Bitlist{0b10010}, root2, sig2) - attslot42 := createAttestation(4, bitfield.Bitlist{0b10001}, root2, sig2) - attSlot5 := createAttestation(5, bitfield.Bitlist{0b1000}, root3, sig3) + aggSlot1_Root1_1 := createAttestation(1, bitfield.Bitlist{0b11100}, root1) + aggSlot1_Root1_2 := createAttestation(1, bitfield.Bitlist{0b10111}, root1) + aggSlot1_Root2 := createAttestation(1, bitfield.Bitlist{0b11100}, root2) + aggSlot2 := createAttestation(2, bitfield.Bitlist{0b11100}, root1) + unaggSlot3_Root1_1 := createAttestation(3, bitfield.Bitlist{0b11000}, root1) + unaggSlot3_Root1_2 := createAttestation(3, bitfield.Bitlist{0b10100}, root1) + unaggSlot3_Root2 := createAttestation(3, bitfield.Bitlist{0b11000}, root2) + unaggSlot4 := createAttestation(4, bitfield.Bitlist{0b11000}, root1) compareResult := func( t *testing.T, @@ -95,17 +97,19 @@ func TestGetAggregateAttestation(t *testing.T) { } pool := attestations.NewPool() + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggSlot3_Root1_1, unaggSlot3_Root1_2, unaggSlot3_Root2, unaggSlot4}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() + require.NoError(t, err) + require.Equal(t, 4, len(unagg), "Expected 4 unaggregated attestations") + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{aggSlot1_Root1_1, aggSlot1_Root1_2, aggSlot1_Root2, aggSlot2}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 4, len(agg), "Expected 4 aggregated attestations") s := &Server{ AttestationsPool: pool, } - t.Run("non-matching attestation request", func(t *testing.T) { - // Test case where no matching attestation exists. - require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") - agg := pool.AggregatedAttestations() - require.Equal(t, 1, len(agg), "Expected 3 aggregated attestations") - s.AttestationsPool = pool - reqRoot, err := attSlot2.Data.HashTreeRoot() + t.Run("non-matching attestation request", func(t *testing.T) { + reqRoot, err := aggSlot2.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" @@ -116,12 +120,7 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, http.StatusNotFound, writer.Code, "Expected HTTP status NotFound for non-matching request") }) t.Run("1 matching aggregated attestation", func(t *testing.T) { - require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") - agg := pool.AggregatedAttestations() - require.Equal(t, 1, len(agg), "Expected 1 aggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot2.Data.HashTreeRoot() + reqRoot, err := aggSlot2.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" @@ -138,18 +137,13 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "2", "0x0e", root1, sig1) + compareResult(t, attestation, "2", hexutil.Encode(aggSlot2.AggregationBits), root1, sig.Marshal()) }) - t.Run("multiple matching aggregated attestations", func(t *testing.T) { - require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attSlot2, attSlot3}), "Failed to save aggregated attestations") - agg := pool.AggregatedAttestations() - require.Equal(t, 3, len(agg), "Expected 3 aggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot2.Data.HashTreeRoot() + t.Run("multiple matching aggregated attestations - return the one with most bits", func(t *testing.T) { + reqRoot, err := aggSlot1_Root1_1.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() @@ -163,16 +157,10 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "2", "0x0e", root1, sig1) + compareResult(t, attestation, "1", hexutil.Encode(aggSlot1_Root1_2.AggregationBits), root1, sig.Marshal()) }) t.Run("1 matching unaggregated attestation", func(t *testing.T) { - require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4}), "Failed to save unaggregated attestations") - unagg, err := pool.UnaggregatedAttestations() - require.NoError(t, err) - require.Equal(t, 1, len(unagg), "Expected 1 unaggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot4.Data.HashTreeRoot() + reqRoot, err := unaggSlot4.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" @@ -188,19 +176,13 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "4", "0x12", root2, sig2) + compareResult(t, attestation, "4", hexutil.Encode(unaggSlot4.AggregationBits), root1, sig.Marshal()) }) - t.Run("only 2 matching unaggregated attestations", func(t *testing.T) { - require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4, attslot42, attSlot5}), "Failed to save unaggregated attestations") - unagg, err := pool.UnaggregatedAttestations() - require.NoError(t, err) - require.Equal(t, 3, len(unagg), "Expected 3 unaggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot4.Data.HashTreeRoot() + t.Run("multiple matching unaggregated attestations - their aggregate is returned", func(t *testing.T) { + reqRoot, err := unaggSlot3_Root1_1.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() @@ -213,10 +195,15 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.Attestation require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "4", "0x12", root2, sig2) + sig1, err := bls.SignatureFromBytes(unaggSlot3_Root1_1.Signature) + require.NoError(t, err) + sig2, err := bls.SignatureFromBytes(unaggSlot3_Root1_2.Signature) + require.NoError(t, err) + expectedSig := bls.AggregateSignatures([]common.Signature{sig1, sig2}) + compareResult(t, attestation, "3", hexutil.Encode(bitfield.Bitlist{0b11100}), root1, expectedSig.Marshal()) }) }) - t.Run("V2", func(t *testing.T) { + /*t.Run("V2", func(t *testing.T) { createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte, sig []byte, bits uint64) *ethpbalpha.AttestationElectra { committeeBits := bitfield.NewBitvector64() committeeBits.SetBitAt(bits, true) @@ -384,7 +371,7 @@ func TestGetAggregateAttestation(t *testing.T) { require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") compareResult(t, attestation, "4", "0x12", root2, sig2, "1") }) - }) + })*/ } func createAttestationData( From 7d168da9340735771f1275e55e7a56c1e4dcd020 Mon Sep 17 00:00:00 2001 From: Saolyn Date: Tue, 5 Nov 2024 16:35:27 +0100 Subject: [PATCH 18/18] v2 tests --- .../rpc/eth/validator/handlers_test.go | 92 ++++++++----------- 1 file changed, 39 insertions(+), 53 deletions(-) diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index 0a3a10d57bdd..b8c3037ba5fe 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -203,25 +203,27 @@ func TestGetAggregateAttestation(t *testing.T) { compareResult(t, attestation, "3", hexutil.Encode(bitfield.Bitlist{0b11100}), root1, expectedSig.Marshal()) }) }) - /*t.Run("V2", func(t *testing.T) { - createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte, sig []byte, bits uint64) *ethpbalpha.AttestationElectra { + t.Run("V2", func(t *testing.T) { + createAttestation := func(slot primitives.Slot, aggregationBits bitfield.Bitlist, root []byte, bits uint64) *ethpbalpha.AttestationElectra { committeeBits := bitfield.NewBitvector64() committeeBits.SetBitAt(bits, true) return ðpbalpha.AttestationElectra{ CommitteeBits: committeeBits, AggregationBits: aggregationBits, - Data: createAttestationData(slot, 1, 1, root), - Signature: sig, + Data: createAttestationData(slot, 0, 1, root), + Signature: sig.Marshal(), } } - attSlot1 := createAttestation(1, bitfield.Bitlist{0b1101}, root1, sig1, 1) - attSlot2 := createAttestation(2, bitfield.Bitlist{0b1110}, root1, sig1, 1) - attSlot3 := createAttestation(3, bitfield.Bitlist{0b1011}, root1, sig1, 1) - attSlot4 := createAttestation(4, bitfield.Bitlist{0b10010}, root2, sig2, 2) - attslot42 := createAttestation(4, bitfield.Bitlist{0b10001}, root2, sig2, 2) - attSlot5 := createAttestation(5, bitfield.Bitlist{0b1000}, root3, sig3, 3) + aggSlot1_Root1_1 := createAttestation(1, bitfield.Bitlist{0b11100}, root1, 1) + aggSlot1_Root1_2 := createAttestation(1, bitfield.Bitlist{0b10111}, root1, 1) + aggSlot1_Root2 := createAttestation(1, bitfield.Bitlist{0b11100}, root2, 1) + aggSlot2 := createAttestation(2, bitfield.Bitlist{0b11100}, root1, 1) + unaggSlot3_Root1_1 := createAttestation(3, bitfield.Bitlist{0b11000}, root1, 1) + unaggSlot3_Root1_2 := createAttestation(3, bitfield.Bitlist{0b10100}, root1, 1) + unaggSlot3_Root2 := createAttestation(3, bitfield.Bitlist{0b11000}, root2, 1) + unaggSlot4 := createAttestation(4, bitfield.Bitlist{0b11000}, root1, 1) compareResult := func( t *testing.T, @@ -236,7 +238,7 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, expectedBits, attestation.CommitteeBits) assert.Equal(t, hexutil.Encode(expectedSig), attestation.Signature, "Signature mismatch") assert.Equal(t, expectedSlot, attestation.Data.Slot, "Slot mismatch in attestation data") - assert.Equal(t, "1", attestation.Data.CommitteeIndex, "Committee index mismatch") + assert.Equal(t, "0", attestation.Data.CommitteeIndex, "Committee index mismatch") assert.Equal(t, hexutil.Encode(expectedRoot), attestation.Data.BeaconBlockRoot, "Beacon block root mismatch") // Source checkpoint checks @@ -251,17 +253,18 @@ func TestGetAggregateAttestation(t *testing.T) { } pool := attestations.NewPool() + require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{unaggSlot3_Root1_1, unaggSlot3_Root1_2, unaggSlot3_Root2, unaggSlot4}), "Failed to save unaggregated attestations") + unagg, err := pool.UnaggregatedAttestations() + require.NoError(t, err) + require.Equal(t, 4, len(unagg), "Expected 4 unaggregated attestations") + require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{aggSlot1_Root1_1, aggSlot1_Root1_2, aggSlot1_Root2, aggSlot2}), "Failed to save aggregated attestations") + agg := pool.AggregatedAttestations() + require.Equal(t, 4, len(agg), "Expected 4 aggregated attestations") s := &Server{ AttestationsPool: pool, } t.Run("non-matching attestation request", func(t *testing.T) { - // Test case where no matching attestation exists. - require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") - agg := pool.AggregatedAttestations() - require.Equal(t, 1, len(agg), "Expected 3 aggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot2.Data.HashTreeRoot() + reqRoot, err := aggSlot2.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=1" + "&committee_index=1" @@ -272,12 +275,7 @@ func TestGetAggregateAttestation(t *testing.T) { assert.Equal(t, http.StatusNotFound, writer.Code, "Expected HTTP status NotFound for non-matching request") }) t.Run("1 matching aggregated attestation", func(t *testing.T) { - require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot2}), "Failed to save aggregated attestations") - agg := pool.AggregatedAttestations() - require.Equal(t, 1, len(agg), "Expected 1 aggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot2.Data.HashTreeRoot() + reqRoot, err := aggSlot2.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=2" + "&committee_index=1" @@ -294,18 +292,13 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.AttestationElectra require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "2", "0x0e", root1, sig1, "1") + compareResult(t, attestation, "2", hexutil.Encode(aggSlot2.AggregationBits), root1, sig.Marshal(), "0x0200000000000000") }) - t.Run("multiple matching aggregated attestations", func(t *testing.T) { - require.NoError(t, pool.SaveAggregatedAttestations([]ethpbalpha.Att{attSlot1, attSlot2, attSlot3}), "Failed to save aggregated attestations") - agg := pool.AggregatedAttestations() - require.Equal(t, 3, len(agg), "Expected 3 aggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot2.Data.HashTreeRoot() + t.Run("multiple matching aggregated attestations - return the one with most bits", func(t *testing.T) { + reqRoot, err := aggSlot1_Root1_1.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") 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=1" + "&committee_index=1" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() @@ -319,16 +312,10 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.AttestationElectra require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "2", "0x0e", root1, sig1, "1") + compareResult(t, attestation, "1", hexutil.Encode(aggSlot1_Root1_2.AggregationBits), root1, sig.Marshal(), "0x0200000000000000") }) t.Run("1 matching unaggregated attestation", func(t *testing.T) { - require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4}), "Failed to save unaggregated attestations") - unagg, err := pool.UnaggregatedAttestations() - require.NoError(t, err) - require.Equal(t, 1, len(unagg), "Expected 1 unaggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot4.Data.HashTreeRoot() + reqRoot, err := unaggSlot4.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" + "&committee_index=1" @@ -344,19 +331,13 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.AttestationElectra require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "4", "0x12", root2, sig2, "1") + compareResult(t, attestation, "4", hexutil.Encode(unaggSlot4.AggregationBits), root1, sig.Marshal(), "0x0200000000000000") }) - t.Run("only 2 matching unaggregated attestations", func(t *testing.T) { - require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpbalpha.Att{attSlot4, attslot42, attSlot5}), "Failed to save unaggregated attestations") - unagg, err := pool.UnaggregatedAttestations() - require.NoError(t, err) - require.Equal(t, 3, len(unagg), "Expected 3 unaggregated attestations") - s.AttestationsPool = pool - - reqRoot, err := attSlot4.Data.HashTreeRoot() + t.Run("multiple matching unaggregated attestations - their aggregate is returned", func(t *testing.T) { + reqRoot, err := unaggSlot3_Root1_1.Data.HashTreeRoot() require.NoError(t, err, "Failed to generate attestation data hash tree root") attDataRoot := hexutil.Encode(reqRoot[:]) - url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=4" + "&committee_index=1" + url := "http://example.com?attestation_data_root=" + attDataRoot + "&slot=3" + "&committee_index=1" request := httptest.NewRequest(http.MethodGet, url, nil) writer := httptest.NewRecorder() @@ -369,9 +350,14 @@ func TestGetAggregateAttestation(t *testing.T) { var attestation structs.AttestationElectra require.NoError(t, json.Unmarshal(resp.Data, &attestation), "Failed to unmarshal attestation data") - compareResult(t, attestation, "4", "0x12", root2, sig2, "1") + sig1, err := bls.SignatureFromBytes(unaggSlot3_Root1_1.Signature) + require.NoError(t, err) + sig2, err := bls.SignatureFromBytes(unaggSlot3_Root1_2.Signature) + require.NoError(t, err) + expectedSig := bls.AggregateSignatures([]common.Signature{sig1, sig2}) + compareResult(t, attestation, "3", hexutil.Encode(bitfield.Bitlist{0b11100}), root1, expectedSig.Marshal(), "0x0200000000000000") }) - })*/ + }) } func createAttestationData(