prysm-pulse/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go
terencechain 0e77a89208
Better version checks for future proof (#11825)
* Better version checks for future proof

* Revert changes for blob

* Use if else

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-01-04 03:47:20 +00:00

279 lines
9.4 KiB
Go

package validator
import (
"context"
"sort"
"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/altair"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v3/config/params"
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/attestation/aggregation"
attaggregation "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/attestation/aggregation/attestations"
"github.com/prysmaticlabs/prysm/v3/runtime/version"
"go.opencensus.io/trace"
)
type proposerAtts []*ethpb.Attestation
func (vs *Server) packAttestations(ctx context.Context, latestState state.BeaconState) ([]*ethpb.Attestation, error) {
ctx, span := trace.StartSpan(ctx, "ProposerServer.packAttestations")
defer span.End()
atts := vs.AttPool.AggregatedAttestations()
atts, err := vs.validateAndDeleteAttsInPool(ctx, latestState, atts)
if err != nil {
return nil, errors.Wrap(err, "could not filter attestations")
}
uAtts, err := vs.AttPool.UnaggregatedAttestations()
if err != nil {
return nil, errors.Wrap(err, "could not get unaggregated attestations")
}
uAtts, err = vs.validateAndDeleteAttsInPool(ctx, latestState, uAtts)
if err != nil {
return nil, errors.Wrap(err, "could not filter attestations")
}
atts = append(atts, uAtts...)
// Remove duplicates from both aggregated/unaggregated attestations. This
// prevents inefficient aggregates being created.
atts, err = proposerAtts(atts).dedup()
if err != nil {
return nil, err
}
attsByDataRoot := make(map[[32]byte][]*ethpb.Attestation, len(atts))
for _, att := range atts {
attDataRoot, err := att.Data.HashTreeRoot()
if err != nil {
return nil, err
}
attsByDataRoot[attDataRoot] = append(attsByDataRoot[attDataRoot], att)
}
attsForInclusion := proposerAtts(make([]*ethpb.Attestation, 0))
for _, as := range attsByDataRoot {
as, err := attaggregation.Aggregate(as)
if err != nil {
return nil, err
}
attsForInclusion = append(attsForInclusion, as...)
}
deduped, err := attsForInclusion.dedup()
if err != nil {
return nil, err
}
sorted, err := deduped.sortByProfitability()
if err != nil {
return nil, err
}
atts = sorted.limitToMaxAttestations()
return atts, nil
}
// filter separates attestation list into two groups: valid and invalid attestations.
// The first group passes the all the required checks for attestation to be considered for proposing.
// And attestations from the second group should be deleted.
func (a proposerAtts) filter(ctx context.Context, st state.BeaconState) (proposerAtts, proposerAtts) {
validAtts := make([]*ethpb.Attestation, 0, len(a))
invalidAtts := make([]*ethpb.Attestation, 0, len(a))
var attestationProcessor func(context.Context, state.BeaconState, *ethpb.Attestation) (state.BeaconState, error)
if st.Version() == version.Phase0 {
attestationProcessor = blocks.ProcessAttestationNoVerifySignature
} else if st.Version() >= version.Altair {
// Use a wrapper here, as go needs strong typing for the function signature.
attestationProcessor = func(ctx context.Context, st state.BeaconState, attestation *ethpb.Attestation) (state.BeaconState, error) {
totalBalance, err := helpers.TotalActiveBalance(st)
if err != nil {
return nil, err
}
return altair.ProcessAttestationNoVerifySignature(ctx, st, attestation, totalBalance)
}
} else {
// Exit early if there is an unknown state type.
return validAtts, invalidAtts
}
for _, att := range a {
if _, err := attestationProcessor(ctx, st, att); err == nil {
validAtts = append(validAtts, att)
continue
}
invalidAtts = append(invalidAtts, att)
}
return validAtts, invalidAtts
}
// sortByProfitability orders attestations by highest slot and by highest aggregation bit count.
func (a proposerAtts) sortByProfitability() (proposerAtts, error) {
if len(a) < 2 {
return a, nil
}
return a.sortByProfitabilityUsingMaxCover()
}
// sortByProfitabilityUsingMaxCover orders attestations by highest slot and by highest aggregation bit count.
// Duplicate bits are counted only once, using max-cover algorithm.
func (a proposerAtts) sortByProfitabilityUsingMaxCover() (proposerAtts, error) {
// Separate attestations by slot, as slot number takes higher precedence when sorting.
var slots []types.Slot
attsBySlot := map[types.Slot]proposerAtts{}
for _, att := range a {
if _, ok := attsBySlot[att.Data.Slot]; !ok {
slots = append(slots, att.Data.Slot)
}
attsBySlot[att.Data.Slot] = append(attsBySlot[att.Data.Slot], att)
}
selectAtts := func(atts proposerAtts) (proposerAtts, error) {
if len(atts) < 2 {
return atts, nil
}
candidates := make([]*bitfield.Bitlist64, len(atts))
for i := 0; i < len(atts); i++ {
var err error
candidates[i], err = atts[i].AggregationBits.ToBitlist64()
if err != nil {
return nil, err
}
}
// Add selected candidates on top, those that are not selected - append at bottom.
selectedKeys, _, err := aggregation.MaxCover(candidates, len(candidates), true /* allowOverlaps */)
if err == nil {
// Pick selected attestations first, leftover attestations will be appended at the end.
// Both lists will be sorted by number of bits set.
selectedAtts := make(proposerAtts, selectedKeys.Count())
leftoverAtts := make(proposerAtts, selectedKeys.Not().Count())
for i, key := range selectedKeys.BitIndices() {
selectedAtts[i] = atts[key]
}
for i, key := range selectedKeys.Not().BitIndices() {
leftoverAtts[i] = atts[key]
}
sort.Slice(selectedAtts, func(i, j int) bool {
return selectedAtts[i].AggregationBits.Count() > selectedAtts[j].AggregationBits.Count()
})
sort.Slice(leftoverAtts, func(i, j int) bool {
return leftoverAtts[i].AggregationBits.Count() > leftoverAtts[j].AggregationBits.Count()
})
return append(selectedAtts, leftoverAtts...), nil
}
return atts, nil
}
// Select attestations. Slots are sorted from higher to lower at this point. Within slots attestations
// are sorted to maximize profitability (greedily selected, with previous attestations' bits
// evaluated before including any new attestation).
var sortedAtts proposerAtts
sort.Slice(slots, func(i, j int) bool {
return slots[i] > slots[j]
})
for _, slot := range slots {
selected, err := selectAtts(attsBySlot[slot])
if err != nil {
return nil, err
}
sortedAtts = append(sortedAtts, selected...)
}
return sortedAtts, nil
}
// limitToMaxAttestations limits attestations to maximum attestations per block.
func (a proposerAtts) limitToMaxAttestations() proposerAtts {
if uint64(len(a)) > params.BeaconConfig().MaxAttestations {
return a[:params.BeaconConfig().MaxAttestations]
}
return a
}
// dedup removes duplicate attestations (ones with the same bits set on).
// Important: not only exact duplicates are removed, but proper subsets are removed too
// (their known bits are redundant and are already contained in their supersets).
func (a proposerAtts) dedup() (proposerAtts, error) {
if len(a) < 2 {
return a, nil
}
attsByDataRoot := make(map[[32]byte][]*ethpb.Attestation, len(a))
for _, att := range a {
attDataRoot, err := att.Data.HashTreeRoot()
if err != nil {
continue
}
attsByDataRoot[attDataRoot] = append(attsByDataRoot[attDataRoot], att)
}
uniqAtts := make([]*ethpb.Attestation, 0, len(a))
for _, atts := range attsByDataRoot {
for i := 0; i < len(atts); i++ {
a := atts[i]
for j := i + 1; j < len(atts); j++ {
b := atts[j]
if c, err := a.AggregationBits.Contains(b.AggregationBits); err != nil {
return nil, err
} else if c {
// a contains b, b is redundant.
atts[j] = atts[len(atts)-1]
atts[len(atts)-1] = nil
atts = atts[:len(atts)-1]
j--
} else if c, err := b.AggregationBits.Contains(a.AggregationBits); err != nil {
return nil, err
} else if c {
// b contains a, a is redundant.
atts[i] = atts[len(atts)-1]
atts[len(atts)-1] = nil
atts = atts[:len(atts)-1]
i--
break
}
}
}
uniqAtts = append(uniqAtts, atts...)
}
return uniqAtts, nil
}
// This filters the input attestations to return a list of valid attestations to be packaged inside a beacon block.
func (vs *Server) validateAndDeleteAttsInPool(ctx context.Context, st state.BeaconState, atts []*ethpb.Attestation) ([]*ethpb.Attestation, error) {
ctx, span := trace.StartSpan(ctx, "ProposerServer.validateAndDeleteAttsInPool")
defer span.End()
validAtts, invalidAtts := proposerAtts(atts).filter(ctx, st)
if err := vs.deleteAttsInPool(ctx, invalidAtts); err != nil {
return nil, err
}
return validAtts, nil
}
// The input attestations are processed and seen by the node, this deletes them from pool
// so proposers don't include them in a block for the future.
func (vs *Server) deleteAttsInPool(ctx context.Context, atts []*ethpb.Attestation) error {
ctx, span := trace.StartSpan(ctx, "ProposerServer.deleteAttsInPool")
defer span.End()
for _, att := range atts {
if ctx.Err() != nil {
return ctx.Err()
}
if helpers.IsAggregated(att) {
if err := vs.AttPool.DeleteAggregatedAttestation(att); err != nil {
return err
}
} else {
if err := vs.AttPool.DeleteUnaggregatedAttestation(att); err != nil {
return err
}
}
}
return nil
}