mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 15:37:56 -05:00
Committee-aware attestation packing (#14245)
* initial algorithm * ready for review * feature flag * typo * review * comment fix * fix TestProposer_sort_DifferentCommittees * flag usage
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
@@ -112,18 +113,12 @@ func (vs *Server) packAttestations(ctx context.Context, latestState state.Beacon
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sorted, err := deduped.sortByProfitability()
|
||||
sorted, err := deduped.sort()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
atts = sorted.limitToMaxAttestations()
|
||||
|
||||
atts, err = vs.filterAttestationBySignature(ctx, atts, latestState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return atts, nil
|
||||
return vs.filterAttestationBySignature(ctx, atts, latestState)
|
||||
}
|
||||
|
||||
// filter separates attestation list into two groups: valid and invalid attestations.
|
||||
@@ -143,14 +138,6 @@ func (a proposerAtts) filter(ctx context.Context, st state.BeaconState) (propose
|
||||
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) {
|
||||
@@ -218,6 +205,143 @@ func (a proposerAtts) sortByProfitabilityUsingMaxCover() (proposerAtts, error) {
|
||||
return sortedAtts, nil
|
||||
}
|
||||
|
||||
// sort attestations as follows:
|
||||
//
|
||||
// - all attestations selected by max-cover are taken, leftover attestations are discarded
|
||||
// (with current parameters all bits of a leftover attestation are already covered by selected attestations)
|
||||
// - selected attestations are ordered by slot, with higher slot coming first
|
||||
// - within a slot, all top attestations (one per committee) are ordered before any second-best attestations, second-best before third-best etc.
|
||||
// - within top/second-best/etc. attestations (one per committee), attestations are ordered by bit count, with higher bit count coming first
|
||||
func (a proposerAtts) sort() (proposerAtts, error) {
|
||||
if len(a) < 2 {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
if features.Get().EnableCommitteeAwarePacking {
|
||||
return a.sortBySlotAndCommittee()
|
||||
}
|
||||
return a.sortByProfitabilityUsingMaxCover()
|
||||
}
|
||||
|
||||
// Separate attestations by slot, as slot number takes higher precedence when sorting.
|
||||
// Also separate by committee index because maxcover will prefer attestations for the same
|
||||
// committee with disjoint bits over attestations for different committees with overlapping
|
||||
// bits, even though same bits for different committees are separate votes.
|
||||
func (a proposerAtts) sortBySlotAndCommittee() (proposerAtts, error) {
|
||||
type slotAtts struct {
|
||||
candidates map[primitives.CommitteeIndex]proposerAtts
|
||||
selected map[primitives.CommitteeIndex]proposerAtts
|
||||
leftover map[primitives.CommitteeIndex]proposerAtts
|
||||
}
|
||||
|
||||
var slots []primitives.Slot
|
||||
attsBySlot := map[primitives.Slot]*slotAtts{}
|
||||
for _, att := range a {
|
||||
slot := att.GetData().Slot
|
||||
ci := att.GetData().CommitteeIndex
|
||||
if _, ok := attsBySlot[slot]; !ok {
|
||||
attsBySlot[slot] = &slotAtts{}
|
||||
attsBySlot[slot].candidates = make(map[primitives.CommitteeIndex]proposerAtts)
|
||||
slots = append(slots, slot)
|
||||
}
|
||||
attsBySlot[slot].candidates[ci] = append(attsBySlot[slot].candidates[ci], att)
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, sa := range attsBySlot {
|
||||
sa.selected = make(map[primitives.CommitteeIndex]proposerAtts)
|
||||
sa.leftover = make(map[primitives.CommitteeIndex]proposerAtts)
|
||||
for ci, committeeAtts := range sa.candidates {
|
||||
sa.selected[ci], err = committeeAtts.sortByProfitabilityUsingMaxCover_committeeAwarePacking()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sortedAtts proposerAtts
|
||||
sort.Slice(slots, func(i, j int) bool {
|
||||
return slots[i] > slots[j]
|
||||
})
|
||||
for _, slot := range slots {
|
||||
sortedAtts = append(sortedAtts, sortSlotAttestations(attsBySlot[slot].selected)...)
|
||||
}
|
||||
for _, slot := range slots {
|
||||
sortedAtts = append(sortedAtts, sortSlotAttestations(attsBySlot[slot].leftover)...)
|
||||
}
|
||||
|
||||
return sortedAtts, nil
|
||||
}
|
||||
|
||||
// sortByProfitabilityUsingMaxCover orders attestations by highest aggregation bit count.
|
||||
// Duplicate bits are counted only once, using max-cover algorithm.
|
||||
func (a proposerAtts) sortByProfitabilityUsingMaxCover_committeeAwarePacking() (proposerAtts, error) {
|
||||
if len(a) < 2 {
|
||||
return a, nil
|
||||
}
|
||||
candidates := make([]*bitfield.Bitlist64, len(a))
|
||||
for i := 0; i < len(a); i++ {
|
||||
var err error
|
||||
candidates[i], err = a[i].GetAggregationBits().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 {
|
||||
log.WithError(err).Debug("MaxCover aggregation failed")
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Pick selected attestations first, leftover attestations will be appended at the end.
|
||||
// Both lists will be sorted by number of bits set.
|
||||
selected := make(proposerAtts, selectedKeys.Count())
|
||||
for i, key := range selectedKeys.BitIndices() {
|
||||
selected[i] = a[key]
|
||||
}
|
||||
sort.Slice(selected, func(i, j int) bool {
|
||||
return selected[i].GetAggregationBits().Count() > selected[j].GetAggregationBits().Count()
|
||||
})
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// sortSlotAttestations assumes each proposerAtts value in the map is ordered by profitability.
|
||||
// The function takes the first attestation from each value, orders these attestations by bit count
|
||||
// and places them at the start of the resulting slice. It then takes the second attestation for each value,
|
||||
// orders these attestations by bit count and appends them to the end.
|
||||
// It continues this pattern until all attestations are processed.
|
||||
func sortSlotAttestations(slotAtts map[primitives.CommitteeIndex]proposerAtts) proposerAtts {
|
||||
attCount := 0
|
||||
for _, committeeAtts := range slotAtts {
|
||||
attCount += len(committeeAtts)
|
||||
}
|
||||
|
||||
sorted := make([]ethpb.Att, 0, attCount)
|
||||
|
||||
processedCount := 0
|
||||
index := 0
|
||||
for processedCount < attCount {
|
||||
var atts []ethpb.Att
|
||||
|
||||
for _, committeeAtts := range slotAtts {
|
||||
if len(committeeAtts) > index {
|
||||
atts = append(atts, committeeAtts[index])
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(atts, func(i, j int) bool {
|
||||
return atts[i].GetAggregationBits().Count() > atts[j].GetAggregationBits().Count()
|
||||
})
|
||||
sorted = append(sorted, atts...)
|
||||
|
||||
processedCount += len(atts)
|
||||
index++
|
||||
}
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
// limitToMaxAttestations limits attestations to maximum attestations per block.
|
||||
func (a proposerAtts) limitToMaxAttestations() proposerAtts {
|
||||
if len(a) == 0 {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls/blst"
|
||||
@@ -19,31 +20,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func TestProposer_ProposerAtts_sortByProfitability(t *testing.T) {
|
||||
atts := proposerAtts([]ethpb.Att{
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 2}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11110000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 3}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
})
|
||||
want := proposerAtts([]ethpb.Att{
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11110000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 4}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 3}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 2}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11100000}}),
|
||||
util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11000000}}),
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
}
|
||||
|
||||
func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
func TestProposer_ProposerAtts_sort(t *testing.T) {
|
||||
type testData struct {
|
||||
slot primitives.Slot
|
||||
bits bitfield.Bitlist
|
||||
@@ -60,7 +37,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
t.Run("no atts", func(t *testing.T) {
|
||||
atts := getAtts([]testData{})
|
||||
want := getAtts([]testData{})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -74,7 +51,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -90,7 +67,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -108,7 +85,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -129,7 +106,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{1, bitfield.Bitlist{0b00001100, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11001000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -154,14 +131,14 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("selected and non selected atts sorted by bit count", func(t *testing.T) {
|
||||
t.Run("follows max-cover", func(t *testing.T) {
|
||||
// Items at slot 4, must be first split into two lists by max-cover, with
|
||||
// 0b10000011 scoring higher (as it provides more info in addition to already selected
|
||||
// attestations) than 0b11100001 (despite naive bit count suggesting otherwise). Then,
|
||||
@@ -186,7 +163,7 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sortByProfitability()
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -194,6 +171,241 @@ func TestProposer_ProposerAtts_sortByProfitabilityUsingMaxCover(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestProposer_ProposerAtts_committeeAwareSort(t *testing.T) {
|
||||
type testData struct {
|
||||
slot primitives.Slot
|
||||
bits bitfield.Bitlist
|
||||
}
|
||||
getAtts := func(data []testData) proposerAtts {
|
||||
var atts proposerAtts
|
||||
for _, att := range data {
|
||||
atts = append(atts, util.HydrateAttestation(ðpb.Attestation{
|
||||
Data: ðpb.AttestationData{Slot: att.slot}, AggregationBits: att.bits}))
|
||||
}
|
||||
return atts
|
||||
}
|
||||
|
||||
t.Run("no atts", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{})
|
||||
want := getAtts([]testData{})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("single att", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("single att per slot", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("two atts on one of the slots", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("compare to native sort", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
// The max-cover based approach will select 0b00001100 instead, despite lower bit count
|
||||
// (since it has two new/unknown bits).
|
||||
t.Run("max-cover", func(t *testing.T) {
|
||||
atts := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000011, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11001000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b00001100, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{1, bitfield.Bitlist{0b11000011, 0b1}},
|
||||
{1, bitfield.Bitlist{0b00001100, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("multiple slots", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
atts := getAtts([]testData{
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11110000, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
|
||||
t.Run("follows max-cover", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
// Items at slot 4 must be first split into two lists by max-cover, with
|
||||
// 0b10000011 being selected and 0b11100001 being leftover (despite naive bit count suggesting otherwise).
|
||||
atts := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b00000001, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11100001, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b10000011, 0b1}},
|
||||
{4, bitfield.Bitlist{0b11111000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
})
|
||||
want := getAtts([]testData{
|
||||
{4, bitfield.Bitlist{0b11111000, 0b1}},
|
||||
{4, bitfield.Bitlist{0b10000011, 0b1}},
|
||||
{3, bitfield.Bitlist{0b11000000, 0b1}},
|
||||
{2, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
{1, bitfield.Bitlist{0b11100000, 0b1}},
|
||||
})
|
||||
atts, err := atts.sort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.DeepEqual(t, want, atts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProposer_sort_DifferentCommittees(t *testing.T) {
|
||||
t.Run("one att per committee", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 1}})
|
||||
c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11100000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 2}})
|
||||
atts := proposerAtts{c1_a1, c2_a1}
|
||||
atts, err := atts.sort()
|
||||
require.NoError(t, err)
|
||||
want := proposerAtts{c1_a1, c2_a1}
|
||||
assert.DeepEqual(t, want, atts)
|
||||
})
|
||||
t.Run("multiple atts per committee", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111100, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 1}})
|
||||
c1_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b10000010, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 1}})
|
||||
c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11110000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 2}})
|
||||
c2_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11100000, 0b1}, Data: ðpb.AttestationData{CommitteeIndex: 2}})
|
||||
atts := proposerAtts{c1_a1, c1_a2, c2_a1, c2_a2}
|
||||
atts, err := atts.sort()
|
||||
require.NoError(t, err)
|
||||
|
||||
want := proposerAtts{c1_a1, c2_a1, c1_a2}
|
||||
assert.DeepEqual(t, want, atts)
|
||||
})
|
||||
t.Run("multiple atts per committee, multiple slots", func(t *testing.T) {
|
||||
feat := features.Get()
|
||||
feat.EnableCommitteeAwarePacking = true
|
||||
reset := features.InitWithReset(feat)
|
||||
defer reset()
|
||||
|
||||
s2_c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111100, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 1}})
|
||||
s2_c1_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b10000010, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 1}})
|
||||
s2_c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11110000, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 2}})
|
||||
s2_c2_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11000000, 0b1}, Data: ðpb.AttestationData{Slot: 2, CommitteeIndex: 2}})
|
||||
s1_c1_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11111100, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 1}})
|
||||
s1_c1_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b10000010, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 1}})
|
||||
s1_c2_a1 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11110000, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 2}})
|
||||
s1_c2_a2 := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.Bitlist{0b11000000, 0b1}, Data: ðpb.AttestationData{Slot: 1, CommitteeIndex: 2}})
|
||||
|
||||
// Arrange in some random order
|
||||
atts := proposerAtts{s1_c1_a1, s2_c1_a2, s1_c2_a2, s2_c2_a2, s1_c2_a1, s2_c2_a1, s1_c1_a2, s2_c1_a1}
|
||||
|
||||
atts, err := atts.sort()
|
||||
require.NoError(t, err)
|
||||
|
||||
want := proposerAtts{s2_c1_a1, s2_c2_a1, s2_c1_a2, s1_c1_a1, s1_c2_a1, s1_c1_a2}
|
||||
assert.DeepEqual(t, want, atts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProposer_ProposerAtts_dedup(t *testing.T) {
|
||||
data1 := util.HydrateAttestationData(ðpb.AttestationData{
|
||||
Slot: 4,
|
||||
|
||||
@@ -49,7 +49,7 @@ func BenchmarkProposerAtts_sortByProfitability(b *testing.B) {
|
||||
for i, att := range atts {
|
||||
attsCopy[i] = att.(*ethpb.Attestation).Copy()
|
||||
}
|
||||
_, err := attsCopy.sortByProfitability()
|
||||
_, err := attsCopy.sort()
|
||||
require.NoError(b, err, "Could not sort attestations by profitability")
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ type Flags struct {
|
||||
EnableDoppelGanger bool // EnableDoppelGanger enables doppelganger protection on startup for the validator.
|
||||
EnableHistoricalSpaceRepresentation bool // EnableHistoricalSpaceRepresentation enables the saving of registry validators in separate buckets to save space
|
||||
EnableBeaconRESTApi bool // EnableBeaconRESTApi enables experimental usage of the beacon REST API by the validator when querying a beacon node
|
||||
EnableCommitteeAwarePacking bool // EnableCommitteeAwarePacking TODO
|
||||
// Logging related toggles.
|
||||
DisableGRPCConnectionLogs bool // Disables logging when a new grpc client has connected.
|
||||
EnableFullSSZDataLogging bool // Enables logging for full ssz data on rejected gossip messages
|
||||
@@ -254,6 +255,10 @@ func ConfigureBeaconChain(ctx *cli.Context) error {
|
||||
logEnabled(EnableQUIC)
|
||||
cfg.EnableQUIC = true
|
||||
}
|
||||
if ctx.IsSet(EnableCommitteeAwarePacking.Name) {
|
||||
logEnabled(EnableCommitteeAwarePacking)
|
||||
cfg.EnableCommitteeAwarePacking = true
|
||||
}
|
||||
|
||||
cfg.AggregateIntervals = [3]time.Duration{aggregateFirstInterval.Value, aggregateSecondInterval.Value, aggregateThirdInterval.Value}
|
||||
Init(cfg)
|
||||
|
||||
@@ -166,6 +166,10 @@ var (
|
||||
Name: "enable-quic",
|
||||
Usage: "Enables connection using the QUIC protocol for peers which support it.",
|
||||
}
|
||||
EnableCommitteeAwarePacking = &cli.BoolFlag{
|
||||
Name: "enable-committee-aware-packing",
|
||||
Usage: "Changes the attestation packing algorithm to one that is aware of attesting committees.",
|
||||
}
|
||||
)
|
||||
|
||||
// devModeFlags holds list of flags that are set when development mode is on.
|
||||
@@ -223,6 +227,7 @@ var BeaconChainFlags = append(deprecatedBeaconFlags, append(deprecatedFlags, []c
|
||||
EnableLightClient,
|
||||
BlobSaveFsync,
|
||||
EnableQUIC,
|
||||
EnableCommitteeAwarePacking,
|
||||
}...)...)
|
||||
|
||||
// E2EBeaconChainFlags contains a list of the beacon chain feature flags to be tested in E2E.
|
||||
|
||||
@@ -663,6 +663,18 @@ func TestMaxCover_MaxCover(t *testing.T) {
|
||||
}},
|
||||
wantedErr: "empty bitlists: invalid max_cover problem",
|
||||
},
|
||||
{
|
||||
name: "doesn't select bitlist which is a subset of another bitlist",
|
||||
args: args{k: 3, allowOverlaps: true, candidates: []*bitfield.Bitlist64{
|
||||
bitfield.NewBitlist64From([]uint64{0b00011100}),
|
||||
bitfield.NewBitlist64From([]uint64{0b00011110}),
|
||||
bitfield.NewBitlist64From([]uint64{0b00000001}),
|
||||
}},
|
||||
want: &BitSetAggregation{
|
||||
Coverage: bitfield.NewBitlist64From([]uint64{0b00011111}),
|
||||
Keys: []int{1, 2},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user