-
Notifications
You must be signed in to change notification settings - Fork 1k
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 /eth/v2/validator/aggregate_attestation
#14481
Open
saolyn
wants to merge
21
commits into
develop
Choose a base branch
from
add-aggregateAttestationV2
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+474
−311
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
97aa3ec
add endpoint
saolyn f9903c4
changelog
saolyn cbd5dd4
fix tests
saolyn 607cd31
fix endpoint
saolyn fb8e325
Merge branch 'develop' into add-aggregateAttestationV2
saolyn f189e41
remove useless broken code
saolyn cb62038
review + fix endpoint
saolyn f94403f
gaz
saolyn 8f8ef67
fix aggregate selection proof test
saolyn 99558f4
fixes
saolyn 3a4acbd
new way of aggregating
rkapka f1a1591
nit
saolyn 55f78d9
fix part of the tests
saolyn 29fc4f7
fix tests
saolyn 3048541
Merge branch 'develop' into add-aggregateAttestationV2
saolyn b980b91
cleanup
saolyn 8f88cb4
fix AggSelectionProof test
rkapka a358698
tests
saolyn 3ee2e54
v1 tests
rkapka 7d168da
v2 tests
saolyn ead84cd
Merge branch 'develop' into add-aggregateAttestationV2
saolyn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,11 +2,13 @@ package validator | |
|
||
import ( | ||
"bytes" | ||
"cmp" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"slices" | ||
"sort" | ||
"strconv" | ||
"time" | ||
|
@@ -32,6 +34,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" | ||
|
@@ -48,71 +51,159 @@ func (s *Server) GetAggregateAttestation(w http.ResponseWriter, r *http.Request) | |
if !ok { | ||
return | ||
} | ||
|
||
_, slot, ok := shared.UintFromQuery(w, r, "slot", true) | ||
if !ok { | ||
return | ||
} | ||
|
||
var match ethpbalpha.Att | ||
var err error | ||
|
||
match, err = matchingAtt(s.AttestationsPool.AggregatedAttestations(), primitives.Slot(slot), attDataRoot) | ||
agg := s.aggregatedAttestation(w, primitives.Slot(slot), attDataRoot, 0) | ||
if agg == nil { | ||
return | ||
} | ||
typedAgg, ok := agg.(*ethpbalpha.Attestation) | ||
if !ok { | ||
httputil.HandleError(w, fmt.Sprintf("Attestation is not of type %T", ðpbalpha.Attestation{}), http.StatusInternalServerError) | ||
return | ||
} | ||
data, err := json.Marshal(structs.AttFromConsensus(typedAgg)) | ||
if err != nil { | ||
httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) | ||
httputil.HandleError(w, "Could not marshal attestation: "+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.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 | ||
} | ||
if match == nil { | ||
atts, err := s.AttestationsPool.UnaggregatedAttestations() | ||
|
||
agg := s.aggregatedAttestation(w, primitives.Slot(slot), attDataRoot, primitives.CommitteeIndex(index)) | ||
if agg == nil { | ||
return | ||
} | ||
resp := &structs.AggregateAttestationResponse{ | ||
Version: version.String(agg.Version()), | ||
} | ||
if agg.Version() >= version.Electra { | ||
typedAgg, ok := agg.(*ethpbalpha.AttestationElectra) | ||
if !ok { | ||
httputil.HandleError(w, fmt.Sprintf("Attestation is not of type %T", ðpbalpha.AttestationElectra{}), http.StatusInternalServerError) | ||
return | ||
} | ||
data, err := json.Marshal(structs.AttElectraFromConsensus(typedAgg)) | ||
if err != nil { | ||
httputil.HandleError(w, "Could not get unaggregated attestations: "+err.Error(), http.StatusInternalServerError) | ||
httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
match, err = matchingAtt(atts, primitives.Slot(slot), attDataRoot) | ||
resp.Data = data | ||
} else { | ||
typedAgg, ok := agg.(*ethpbalpha.Attestation) | ||
if !ok { | ||
httputil.HandleError(w, fmt.Sprintf("Attestation is not of type %T", ðpbalpha.Attestation{}), http.StatusInternalServerError) | ||
return | ||
} | ||
data, err := json.Marshal(structs.AttFromConsensus(typedAgg)) | ||
if err != nil { | ||
httputil.HandleError(w, "Could not get matching attestation: "+err.Error(), http.StatusInternalServerError) | ||
httputil.HandleError(w, "Could not marshal attestation: "+err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
resp.Data = data | ||
} | ||
if match == nil { | ||
httputil.HandleError(w, "No matching attestation found", http.StatusNotFound) | ||
return | ||
httputil.WriteJson(w, resp) | ||
} | ||
|
||
func (s *Server) aggregatedAttestation(w http.ResponseWriter, slot primitives.Slot, attDataRoot []byte, index primitives.CommitteeIndex) ethpbalpha.Att { | ||
var err error | ||
|
||
match, err := matchingAtts(s.AttestationsPool.AggregatedAttestations(), slot, attDataRoot, index) | ||
if err != nil { | ||
httputil.HandleError(w, "Could not get matching attestations: "+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(b.GetAggregationBits().Count(), a.GetAggregationBits().Count()) | ||
}) | ||
return match[0] | ||
} | ||
|
||
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) | ||
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 attestations: "+err.Error(), http.StatusInternalServerError) | ||
return nil | ||
} | ||
if len(match) == 0 { | ||
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 attestations: "+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) (ethpbalpha.Att, error) { | ||
func matchingAtts(atts []ethpbalpha.Att, slot primitives.Slot, attDataRoot []byte, index primitives.CommitteeIndex) ([]ethpbalpha.Att, error) { | ||
if len(atts) == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So that we don't panic in the declaration of |
||
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 bytes.Equal(root[:], attDataRoot) { | ||
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. | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
%T
with an empty struct is nicer in my opinion because if the type name changes, you will get a compiler error