Skip to content

Commit

Permalink
Add GET /eth/v2/beacon/pool/attestations endpoint (#14560)
Browse files Browse the repository at this point in the history
* add ListAttestationsV2 endpoint

* fix endpoint

* changelog

* add endpoint to tests

* add trailing comma

* add version header + lint fix

* all reviews

* modify v1 and comments

* fix linter

* Radek' review
  • Loading branch information
saolyn authored Oct 28, 2024
1 parent 53f1f11 commit 09accc7
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 93 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Added SubmitPoolAttesterSlashingV2 endpoint.
- Added SubmitAggregateAndProofsRequestV2 endpoint.
- Updated the `beacon-chain/monitor` package to Electra. [PR](https://github.com/prysmaticlabs/prysm/pull/14562)
- Added ListAttestationsV2 endpoint.

### Changed

Expand Down
3 changes: 2 additions & 1 deletion api/server/structs/endpoints_beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ type GetCommitteesResponse struct {
}

type ListAttestationsResponse struct {
Data []*Attestation `json:"data"`
Version string `json:"version,omitempty"`
Data json.RawMessage `json:"data"`
}

type SubmitAttestationsRequest struct {
Expand Down
9 changes: 9 additions & 0 deletions beacon-chain/rpc/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,15 @@ func (s *Service) beaconEndpoints(
handler: server.ListAttestations,
methods: []string{http.MethodGet},
},
{
template: "/eth/v2/beacon/pool/attestations",
name: namespace + ".ListAttestationsV2",
middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
handler: server.ListAttestationsV2,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/beacon/pool/attestations",
name: namespace + ".SubmitAttestations",
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/rpc/endpoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func Test_endpoints(t *testing.T) {
"/eth/v1/beacon/deposit_snapshot": {http.MethodGet},
"/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet},
"/eth/v1/beacon/pool/attestations": {http.MethodGet, http.MethodPost},
"/eth/v2/beacon/pool/attestations": {http.MethodGet},
"/eth/v1/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost},
"/eth/v2/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/pool/proposer_slashings": {http.MethodGet, http.MethodPost},
Expand Down
131 changes: 107 additions & 24 deletions beacon-chain/rpc/eth/beacon/handlers_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,39 +55,122 @@ func (s *Server) ListAttestations(w http.ResponseWriter, r *http.Request) {
return
}
attestations = append(attestations, unaggAtts...)
isEmptyReq := rawSlot == "" && rawCommitteeIndex == ""
if isEmptyReq {
allAtts := make([]*structs.Attestation, len(attestations))
for i, att := range attestations {
a, ok := att.(*eth.Attestation)
if ok {
allAtts[i] = structs.AttFromConsensus(a)
} else {
httputil.HandleError(w, fmt.Sprintf("unable to convert attestations of type %T", att), http.StatusInternalServerError)
return
}

filteredAtts := make([]*structs.Attestation, 0, len(attestations))
for _, a := range attestations {
var includeAttestation bool
att, ok := a.(*eth.Attestation)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", a), http.StatusInternalServerError)
return
}

includeAttestation = shouldIncludeAttestation(att.GetData(), rawSlot, slot, rawCommitteeIndex, committeeIndex)
if includeAttestation {
attStruct := structs.AttFromConsensus(att)
filteredAtts = append(filteredAtts, attStruct)
}
httputil.WriteJson(w, &structs.ListAttestationsResponse{Data: allAtts})
}

attsData, err := json.Marshal(filteredAtts)
if err != nil {
httputil.HandleError(w, "Could not marshal attestations: "+err.Error(), http.StatusInternalServerError)
return
}

bothDefined := rawSlot != "" && rawCommitteeIndex != ""
filteredAtts := make([]*structs.Attestation, 0, len(attestations))
httputil.WriteJson(w, &structs.ListAttestationsResponse{
Data: attsData,
})
}

// ListAttestationsV2 retrieves attestations known by the node but
// not necessarily incorporated into any block. Allows filtering by committee index or slot.
func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.ListAttestationsV2")
defer span.End()

rawSlot, slot, ok := shared.UintFromQuery(w, r, "slot", false)
if !ok {
return
}
rawCommitteeIndex, committeeIndex, ok := shared.UintFromQuery(w, r, "committee_index", false)
if !ok {
return
}

headState, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx)
if err != nil {
httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError)
return
}

attestations := s.AttestationsPool.AggregatedAttestations()
unaggAtts, err := s.AttestationsPool.UnaggregatedAttestations()
if err != nil {
httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError)
return
}
attestations = append(attestations, unaggAtts...)

filteredAtts := make([]interface{}, 0, len(attestations))
for _, att := range attestations {
committeeIndexMatch := rawCommitteeIndex != "" && att.GetData().CommitteeIndex == primitives.CommitteeIndex(committeeIndex)
slotMatch := rawSlot != "" && att.GetData().Slot == primitives.Slot(slot)
shouldAppend := (bothDefined && committeeIndexMatch && slotMatch) || (!bothDefined && (committeeIndexMatch || slotMatch))
if shouldAppend {
a, ok := att.(*eth.Attestation)
if ok {
filteredAtts = append(filteredAtts, structs.AttFromConsensus(a))
} else {
httputil.HandleError(w, fmt.Sprintf("unable to convert attestations of type %T", att), http.StatusInternalServerError)
var includeAttestation bool
if headState.Version() >= version.Electra {
attElectra, ok := att.(*eth.AttestationElectra)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError)
return
}

includeAttestation = shouldIncludeAttestation(attElectra.GetData(), rawSlot, slot, rawCommitteeIndex, committeeIndex)
if includeAttestation {
attStruct := structs.AttElectraFromConsensus(attElectra)
filteredAtts = append(filteredAtts, attStruct)
}
} else {
attOld, ok := att.(*eth.Attestation)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError)
return
}

includeAttestation = shouldIncludeAttestation(attOld.GetData(), rawSlot, slot, rawCommitteeIndex, committeeIndex)
if includeAttestation {
attStruct := structs.AttFromConsensus(attOld)
filteredAtts = append(filteredAtts, attStruct)
}
}
}
httputil.WriteJson(w, &structs.ListAttestationsResponse{Data: filteredAtts})

attsData, err := json.Marshal(filteredAtts)
if err != nil {
httputil.HandleError(w, "Could not marshal attestations: "+err.Error(), http.StatusInternalServerError)
return
}

httputil.WriteJson(w, &structs.ListAttestationsResponse{
Version: version.String(headState.Version()),
Data: attsData,
})
}

// Helper function to determine if an attestation should be included
func shouldIncludeAttestation(
data *eth.AttestationData,
rawSlot string,
slot uint64,
rawCommitteeIndex string,
committeeIndex uint64,
) bool {
committeeIndexMatch := true
slotMatch := true
if rawCommitteeIndex != "" && data.CommitteeIndex != primitives.CommitteeIndex(committeeIndex) {
committeeIndexMatch = false
}
if rawSlot != "" && data.Slot != primitives.Slot(slot) {
slotMatch = false
}
return committeeIndexMatch && slotMatch
}

// SubmitAttestations submits an attestation object to node. If the attestation passes all validation
Expand Down
Loading

0 comments on commit 09accc7

Please sign in to comment.