Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GET /eth/v2/beacon/pool/attestations endpoint #14560

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
142 changes: 124 additions & 18 deletions beacon-chain/rpc/eth/beacon/handlers_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,39 +55,145 @@ func (s *Server) ListAttestations(w http.ResponseWriter, r *http.Request) {
return
}
attestations = append(attestations, unaggAtts...)

isEmptyReq := rawSlot == "" && rawCommitteeIndex == ""
bothDefined := rawSlot != "" && rawCommitteeIndex != ""
var attsData json.RawMessage

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)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestations of type %T", att), http.StatusInternalServerError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestations of type %T", att), http.StatusInternalServerError)
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError)

return
}
allAtts[i] = structs.AttFromConsensus(a)
}
attsData, err = json.Marshal(allAtts)
if err != nil {
httputil.HandleError(w, "Could not marshal attestations: "+err.Error(), http.StatusInternalServerError)
return
}
} else {
filteredAtts := make([]*structs.Attestation, 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 {
Copy link
Contributor

@rkapka rkapka Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simplified like this:

committeeIndexMatch := true
slotMatch := true
if rawCommitteeIndex != "" && att.GetData().CommitteeIndex != primitives.CommitteeIndex(committeeIndex) {
    committeeIndexMatch = false
}
if rawSlot != "" && att.GetData().Slot != primitives.Slot(slot) {
    slotMatch = false
}

if committeeIndexMatch && slotMatch {

a, ok := att.(*eth.Attestation)
if !ok {
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestations of type %T", att), http.StatusInternalServerError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestations of type %T", att), http.StatusInternalServerError)
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError)

return
}
filteredAtts = append(filteredAtts, structs.AttFromConsensus(a))
}
}
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
}
}
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) {
_, span := trace.StartSpan(r.Context(), "beacon.ListAttestationsV2")
defer span.End()

rawSlot, slot, ok := shared.UintFromQuery(w, r, "slot", false)
saolyn marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return
}
rawCommitteeIndex, committeeIndex, ok := shared.UintFromQuery(w, r, "committee_index", false)
if !ok {
return
}

bothDefined := rawSlot != "" && rawCommitteeIndex != ""
filteredAtts := make([]*structs.Attestation, 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)
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...)

var firstVersion int
versionSet := false
var attsData json.RawMessage

isEmptyReq := rawSlot == "" && rawCommitteeIndex == ""
if isEmptyReq {
allAtts := make([]interface{}, len(attestations))
for i, att := range attestations {
switch a := att.(type) {
case *eth.AttestationElectra:
allAtts[i] = structs.AttElectraFromConsensus(a)
if !versionSet {
firstVersion = a.Version()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of doing this first version set etc, can't we get the fork from the slot?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with the other function

versionSet = true
}
case *eth.Attestation:
allAtts[i] = structs.AttFromConsensus(a)
if !versionSet {
firstVersion = a.Version()
versionSet = true
}
default:
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError)
return
}
}
attsData, err = json.Marshal(allAtts)
if err != nil {
httputil.HandleError(w, "Could not marshal attestations: "+err.Error(), http.StatusInternalServerError)
return
}
} else {
bothDefined := rawSlot != "" && rawCommitteeIndex != ""
filteredAtts := make([]interface{}, 0, len(attestations))
for _, att := range attestations {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a way to merge these two loops and just append based on these checks right?

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 {
switch a := att.(type) {
case *eth.AttestationElectra:
filteredAtts = append(filteredAtts, structs.AttElectraFromConsensus(a))
if !versionSet {
firstVersion = a.Version()
versionSet = true
}
case *eth.Attestation:
filteredAtts = append(filteredAtts, structs.AttFromConsensus(a))
if !versionSet {
firstVersion = a.Version()
versionSet = true
}
default:
httputil.HandleError(w, fmt.Sprintf("Unable to convert attestation of type %T", att), http.StatusInternalServerError)
return
}
}
}
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{Data: filteredAtts})
w.Header().Set(api.VersionHeader, version.String(firstVersion))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same idea as for slashings: we should use the head state or current slot to determine the version and only return attestations for that version.

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

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