Skip to content

Commit 7c671b0

Browse files
committed
Process committees correctly post-electra
1 parent c3b7b9d commit 7c671b0

8 files changed

Lines changed: 166 additions & 96 deletions

File tree

shared/services/beacon/client.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ type Committees interface {
8282
// Validators returns the list of validators of the committee at
8383
// the provided offset
8484
Validators(int) []string
85+
// ValidatorCount returns the number of validators in the committee at
86+
// the provided offset
87+
ValidatorCount(int) int
8588
// Count returns the number of committees in the response
8689
Count() int
8790
// Release returns the reused validators slice buffer to the pool for
@@ -94,6 +97,26 @@ type AttestationInfo struct {
9497
AggregationBits bitfield.Bitlist
9598
SlotIndex uint64
9699
CommitteeIndex uint64
100+
CommitteeBits bitfield.Bitlist
101+
}
102+
103+
// if !attestation.ValidatorAttested(i, position, slotInfo.CommitteeSizes) {
104+
func (a AttestationInfo) ValidatorAttested(committeeIndex int, position int, committeeSizes map[uint64]int) bool {
105+
// Calculate the offset in aggregation_bits
106+
var offset int
107+
if a.CommitteeBits == nil {
108+
offset = position
109+
} else {
110+
committeeOffset := 0
111+
if committeeIndex > 0 {
112+
for i := 0; i < committeeIndex; i++ {
113+
committeeOffset += committeeSizes[uint64(i)]
114+
}
115+
}
116+
offset = committeeOffset + position
117+
}
118+
119+
return a.AggregationBits.BitAt(uint64(offset))
97120
}
98121

99122
// Beacon client type

shared/services/beacon/client/committee.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ func (c *CommitteesResponse) Validators(idx int) []string {
6565
return c.Data[idx].Validators
6666
}
6767

68+
func (c *CommitteesResponse) ValidatorCount(idx int) int {
69+
return len(c.Data[idx].Validators)
70+
}
71+
6872
func (c *CommitteesResponse) Release() {
6973
for _, committee := range c.Data {
7074
// Reset the slice length to 0 (capacity stays the same)

shared/services/beacon/client/std-http-client.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,12 @@ func (c *StandardHttpClient) GetAttestations(blockId string) ([]beacon.Attestati
621621
if err != nil {
622622
return nil, false, fmt.Errorf("Error decoding aggregation bits for attestation %d of block %s: %w", i, blockId, err)
623623
}
624+
if attestation.CommitteeBits != "" && attestation.CommitteeBits != "0x" {
625+
attestationInfo[i].CommitteeBits, err = hex.DecodeString(attestation.CommitteeBits)
626+
if err != nil {
627+
return nil, false, fmt.Errorf("Error decoding committee bits for attestation %d of block %s: %w", i, blockId, err)
628+
}
629+
}
624630
}
625631

626632
return attestationInfo, true, nil
@@ -661,6 +667,12 @@ func (c *StandardHttpClient) GetBeaconBlock(blockId string) (beacon.BeaconBlock,
661667
if err != nil {
662668
return beacon.BeaconBlock{}, false, fmt.Errorf("Error decoding aggregation bits for attestation %d of block %s: %w", i, blockId, err)
663669
}
670+
if attestation.CommitteeBits != "" && attestation.CommitteeBits != "0x" {
671+
info.CommitteeBits, err = hex.DecodeString(attestation.CommitteeBits)
672+
if err != nil {
673+
return beacon.BeaconBlock{}, false, fmt.Errorf("Error decoding committee bits for attestation %d of block %s: %w", i, blockId, err)
674+
}
675+
}
664676
beaconBlock.Attestations = append(beaconBlock.Attestations, info)
665677
}
666678

shared/services/beacon/client/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ type Attestation struct {
166166
Slot uinteger `json:"slot"`
167167
Index uinteger `json:"index"`
168168
} `json:"data"`
169+
CommitteeBits string `json:"committee_bits"`
169170
}
170171

171172
type Withdrawal struct {

shared/services/rewards/generator-impl-v8.go

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -852,50 +852,58 @@ func (r *treeGeneratorImpl_v8) checkDutiesForSlot(attestations []beacon.Attestat
852852
if inclusionSlot-attestation.SlotIndex > r.beaconConfig.SlotsPerEpoch {
853853
continue
854854
}
855-
rpCommittee, exists := slotInfo.Committees[attestation.CommitteeIndex]
856-
if !exists {
857-
continue
855+
var committeeIndexes []int
856+
if attestation.CommitteeBits == nil {
857+
committeeIndexes = []int{int(attestation.CommitteeIndex)}
858+
} else {
859+
committeeIndexes = attestation.CommitteeBits.BitIndices()
858860
}
859-
blockTime := r.genesisTime.Add(time.Second * time.Duration(r.networkState.BeaconConfig.SecondsPerSlot*attestation.SlotIndex))
860-
861-
// Check if each RP validator attested successfully
862-
for position, validator := range rpCommittee.Positions {
863-
if !attestation.AggregationBits.BitAt(uint64(position)) {
861+
for _, committeeIndex := range committeeIndexes {
862+
rpCommittee, exists := slotInfo.Committees[uint64(committeeIndex)]
863+
if !exists {
864864
continue
865865
}
866+
blockTime := r.genesisTime.Add(time.Second * time.Duration(r.networkState.BeaconConfig.SecondsPerSlot*attestation.SlotIndex))
866867

867-
// This was seen, so remove it from the missing attestations and add it to the completed ones
868-
delete(rpCommittee.Positions, position)
869-
if len(rpCommittee.Positions) == 0 {
870-
delete(slotInfo.Committees, attestation.CommitteeIndex)
871-
}
872-
if len(slotInfo.Committees) == 0 {
873-
delete(r.intervalDutiesInfo.Slots, attestation.SlotIndex)
874-
}
875-
delete(validator.MissingAttestationSlots, attestation.SlotIndex)
868+
// Check if each RP validator attested successfully
869+
for position, validator := range rpCommittee.Positions {
870+
if !attestation.ValidatorAttested(committeeIndex, position, slotInfo.CommitteeSizes) {
871+
continue
872+
}
876873

877-
// Check if this minipool was opted into the SP for this block
878-
nodeDetails := r.nodeDetails[validator.NodeIndex]
879-
if blockTime.Sub(nodeDetails.OptInTime) < 0 || nodeDetails.OptOutTime.Sub(blockTime) < 0 {
880-
// Not opted in
881-
continue
882-
}
874+
// This was seen, so remove it from the missing attestations and add it to the completed ones
875+
delete(rpCommittee.Positions, position)
876+
if len(rpCommittee.Positions) == 0 {
877+
delete(slotInfo.Committees, uint64(committeeIndex))
878+
}
879+
if len(slotInfo.Committees) == 0 {
880+
delete(r.intervalDutiesInfo.Slots, attestation.SlotIndex)
881+
}
882+
delete(validator.MissingAttestationSlots, attestation.SlotIndex)
883+
884+
// Check if this minipool was opted into the SP for this block
885+
nodeDetails := r.nodeDetails[validator.NodeIndex]
886+
if blockTime.Sub(nodeDetails.OptInTime) < 0 || nodeDetails.OptOutTime.Sub(blockTime) < 0 {
887+
// Not opted in
888+
continue
889+
}
883890

884-
// Mark this duty as completed
885-
validator.CompletedAttestations[attestation.SlotIndex] = true
886-
887-
// Get the pseudoscore for this attestation
888-
details := r.networkState.MinipoolDetailsByAddress[validator.Address]
889-
bond, fee := r.getMinipoolBondAndNodeFee(details, blockTime)
890-
minipoolScore := big.NewInt(0).Sub(one, fee) // 1 - fee
891-
minipoolScore.Mul(minipoolScore, bond) // Multiply by bond
892-
minipoolScore.Div(minipoolScore, validatorReq) // Divide by 32 to get the bond as a fraction of a total validator
893-
minipoolScore.Add(minipoolScore, fee) // Total = fee + (bond/32)(1 - fee)
894-
895-
// Add it to the minipool's score and the total score
896-
validator.AttestationScore.Add(&validator.AttestationScore.Int, minipoolScore)
897-
r.totalAttestationScore.Add(r.totalAttestationScore, minipoolScore)
898-
r.successfulAttestations++
891+
// Mark this duty as completed
892+
validator.CompletedAttestations[attestation.SlotIndex] = true
893+
894+
// Get the pseudoscore for this attestation
895+
details := r.networkState.MinipoolDetailsByAddress[validator.Address]
896+
bond, fee := r.getMinipoolBondAndNodeFee(details, blockTime)
897+
minipoolScore := big.NewInt(0).Sub(one, fee) // 1 - fee
898+
minipoolScore.Mul(minipoolScore, bond) // Multiply by bond
899+
minipoolScore.Div(minipoolScore, validatorReq) // Divide by 32 to get the bond as a fraction of a total validator
900+
minipoolScore.Add(minipoolScore, fee) // Total = fee + (bond/32)(1 - fee)
901+
902+
// Add it to the minipool's score and the total score
903+
validator.AttestationScore.Add(&validator.AttestationScore.Int, minipoolScore)
904+
r.totalAttestationScore.Add(r.totalAttestationScore, minipoolScore)
905+
r.successfulAttestations++
906+
}
899907
}
900908
}
901909

@@ -916,6 +924,18 @@ func (r *treeGeneratorImpl_v8) getDutiesForEpoch(committees beacon.Committees) e
916924
blockTime := r.genesisTime.Add(time.Second * time.Duration(r.beaconConfig.SecondsPerSlot*slotIndex))
917925
committeeIndex := committees.Index(idx)
918926

927+
// Add the committee size to the list, for calculating offset in post-electra aggregation_bits
928+
slotInfo, exists := r.intervalDutiesInfo.Slots[slotIndex]
929+
if !exists {
930+
slotInfo = &SlotInfo{
931+
Index: slotIndex,
932+
Committees: map[uint64]*CommitteeInfo{},
933+
CommitteeSizes: map[uint64]int{},
934+
}
935+
r.intervalDutiesInfo.Slots[slotIndex] = slotInfo
936+
}
937+
slotInfo.CommitteeSizes[committeeIndex] = committees.ValidatorCount(idx)
938+
919939
// Check if there are any RP validators in this committee
920940
rpValidators := map[int]*MinipoolInfo{}
921941
for position, validator := range committees.Validators(idx) {
@@ -948,14 +968,6 @@ func (r *treeGeneratorImpl_v8) getDutiesForEpoch(committees beacon.Committees) e
948968

949969
// If there are some RP validators, add this committee to the map
950970
if len(rpValidators) > 0 {
951-
slotInfo, exists := r.intervalDutiesInfo.Slots[slotIndex]
952-
if !exists {
953-
slotInfo = &SlotInfo{
954-
Index: slotIndex,
955-
Committees: map[uint64]*CommitteeInfo{},
956-
}
957-
r.intervalDutiesInfo.Slots[slotIndex] = slotInfo
958-
}
959971
slotInfo.Committees[committeeIndex] = &CommitteeInfo{
960972
Index: committeeIndex,
961973
Positions: rpValidators,

shared/services/rewards/generator-impl-v9-v10.go

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -948,58 +948,67 @@ func (r *treeGeneratorImpl_v9_v10) checkAttestations(attestations []beacon.Attes
948948
if inclusionSlot-attestation.SlotIndex > r.beaconConfig.SlotsPerEpoch {
949949
continue
950950
}
951-
rpCommittee, exists := slotInfo.Committees[attestation.CommitteeIndex]
952-
if !exists {
953-
continue
951+
var committeeIndexes []int
952+
if attestation.CommitteeBits == nil {
953+
committeeIndexes = []int{int(attestation.CommitteeIndex)}
954+
} else {
955+
committeeIndexes = attestation.CommitteeBits.BitIndices()
954956
}
955-
blockTime := r.genesisTime.Add(time.Second * time.Duration(r.networkState.BeaconConfig.SecondsPerSlot*attestation.SlotIndex))
956957

957-
// Check if each RP validator attested successfully
958-
for position, validator := range rpCommittee.Positions {
959-
if !attestation.AggregationBits.BitAt(uint64(position)) {
958+
for _, committeeIndex := range committeeIndexes {
959+
rpCommittee, exists := slotInfo.Committees[uint64(committeeIndex)]
960+
if !exists {
960961
continue
961962
}
963+
blockTime := r.genesisTime.Add(time.Second * time.Duration(r.networkState.BeaconConfig.SecondsPerSlot*attestation.SlotIndex))
962964

963-
// This was seen, so remove it from the missing attestations and add it to the completed ones
964-
delete(rpCommittee.Positions, position)
965-
if len(rpCommittee.Positions) == 0 {
966-
delete(slotInfo.Committees, attestation.CommitteeIndex)
967-
}
968-
if len(slotInfo.Committees) == 0 {
969-
delete(r.intervalDutiesInfo.Slots, attestation.SlotIndex)
970-
}
971-
delete(validator.MissingAttestationSlots, attestation.SlotIndex)
965+
// Check if each RP validator attested successfully
966+
for position, validator := range rpCommittee.Positions {
967+
if !attestation.ValidatorAttested(committeeIndex, position, slotInfo.CommitteeSizes) {
968+
continue
969+
}
972970

973-
// Check if this minipool was opted into the SP for this block
974-
nodeDetails := r.nodeDetails[validator.NodeIndex]
975-
if blockTime.Before(nodeDetails.OptInTime) || blockTime.After(nodeDetails.OptOutTime) {
976-
// Not opted in
977-
continue
978-
}
971+
// This was seen, so remove it from the missing attestations and add it to the completed ones
972+
delete(rpCommittee.Positions, position)
973+
if len(rpCommittee.Positions) == 0 {
974+
delete(slotInfo.Committees, uint64(committeeIndex))
975+
}
976+
if len(slotInfo.Committees) == 0 {
977+
delete(r.intervalDutiesInfo.Slots, attestation.SlotIndex)
978+
}
979+
delete(validator.MissingAttestationSlots, attestation.SlotIndex)
979980

980-
eligibleBorrowedEth := nodeDetails.EligibleBorrowedEth
981-
_, percentOfBorrowedEth := r.networkState.GetStakedRplValueInEthAndPercentOfBorrowedEth(eligibleBorrowedEth, nodeDetails.RplStake)
981+
// Check if this minipool was opted into the SP for this block
982+
nodeDetails := r.nodeDetails[validator.NodeIndex]
983+
if blockTime.Before(nodeDetails.OptInTime) || blockTime.After(nodeDetails.OptOutTime) {
984+
// Not opted in
985+
continue
986+
}
982987

983-
// Mark this duty as completed
984-
validator.CompletedAttestations[attestation.SlotIndex] = true
988+
eligibleBorrowedEth := nodeDetails.EligibleBorrowedEth
989+
_, percentOfBorrowedEth := r.networkState.GetStakedRplValueInEthAndPercentOfBorrowedEth(eligibleBorrowedEth, nodeDetails.RplStake)
985990

986-
// Get the pseudoscore for this attestation
987-
details := r.networkState.MinipoolDetailsByAddress[validator.Address]
988-
bond, fee := details.GetMinipoolBondAndNodeFee(blockTime)
991+
// Mark this duty as completed
992+
validator.CompletedAttestations[attestation.SlotIndex] = true
989993

990-
if r.rewardsFile.RulesetVersion >= 10 {
991-
fee = fees.GetMinipoolFeeWithBonus(bond, fee, percentOfBorrowedEth)
992-
}
994+
// Get the pseudoscore for this attestation
995+
details := r.networkState.MinipoolDetailsByAddress[validator.Address]
996+
bond, fee := details.GetMinipoolBondAndNodeFee(blockTime)
993997

994-
minipoolScore := big.NewInt(0).Sub(oneEth, fee) // 1 - fee
995-
minipoolScore.Mul(minipoolScore, bond) // Multiply by bond
996-
minipoolScore.Div(minipoolScore, thirtyTwoEth) // Divide by 32 to get the bond as a fraction of a total validator
997-
minipoolScore.Add(minipoolScore, fee) // Total = fee + (bond/32)(1 - fee)
998+
if r.rewardsFile.RulesetVersion >= 10 {
999+
fee = fees.GetMinipoolFeeWithBonus(bond, fee, percentOfBorrowedEth)
1000+
}
9981001

999-
// Add it to the minipool's score and the total score
1000-
validator.AttestationScore.Add(&validator.AttestationScore.Int, minipoolScore)
1001-
r.totalAttestationScore.Add(r.totalAttestationScore, minipoolScore)
1002-
r.successfulAttestations++
1002+
minipoolScore := big.NewInt(0).Sub(oneEth, fee) // 1 - fee
1003+
minipoolScore.Mul(minipoolScore, bond) // Multiply by bond
1004+
minipoolScore.Div(minipoolScore, thirtyTwoEth) // Divide by 32 to get the bond as a fraction of a total validator
1005+
minipoolScore.Add(minipoolScore, fee) // Total = fee + (bond/32)(1 - fee)
1006+
1007+
// Add it to the minipool's score and the total score
1008+
validator.AttestationScore.Add(&validator.AttestationScore.Int, minipoolScore)
1009+
r.totalAttestationScore.Add(r.totalAttestationScore, minipoolScore)
1010+
r.successfulAttestations++
1011+
}
10031012
}
10041013
}
10051014

@@ -1020,6 +1029,18 @@ func (r *treeGeneratorImpl_v9_v10) getDutiesForEpoch(committees beacon.Committee
10201029
blockTime := r.genesisTime.Add(time.Second * time.Duration(r.beaconConfig.SecondsPerSlot*slotIndex))
10211030
committeeIndex := committees.Index(idx)
10221031

1032+
// Add the committee size to the list, for calculating offset in post-electra aggregation_bits
1033+
slotInfo, exists := r.intervalDutiesInfo.Slots[slotIndex]
1034+
if !exists {
1035+
slotInfo = &SlotInfo{
1036+
Index: slotIndex,
1037+
Committees: map[uint64]*CommitteeInfo{},
1038+
CommitteeSizes: map[uint64]int{},
1039+
}
1040+
r.intervalDutiesInfo.Slots[slotIndex] = slotInfo
1041+
}
1042+
slotInfo.CommitteeSizes[committeeIndex] = committees.ValidatorCount(idx)
1043+
10231044
// Check if there are any RP validators in this committee
10241045
rpValidators := map[int]*MinipoolInfo{}
10251046
for position, validator := range committees.Validators(idx) {
@@ -1052,14 +1073,6 @@ func (r *treeGeneratorImpl_v9_v10) getDutiesForEpoch(committees beacon.Committee
10521073

10531074
// If there are some RP validators, add this committee to the map
10541075
if len(rpValidators) > 0 {
1055-
slotInfo, exists := r.intervalDutiesInfo.Slots[slotIndex]
1056-
if !exists {
1057-
slotInfo = &SlotInfo{
1058-
Index: slotIndex,
1059-
Committees: map[uint64]*CommitteeInfo{},
1060-
}
1061-
r.intervalDutiesInfo.Slots[slotIndex] = slotInfo
1062-
}
10631076
slotInfo.Committees[committeeIndex] = &CommitteeInfo{
10641077
Index: committeeIndex,
10651078
Positions: rpValidators,

shared/services/rewards/test/beacon.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ func (mbc *MockBeaconCommittees) Validators(index int) []string {
387387
return mbc.slots[index].validators
388388
}
389389

390+
func (mbc *MockBeaconCommittees) ValidatorCount(index int) int {
391+
return len(mbc.slots[index].validators)
392+
}
393+
390394
// Release is a no-op
391395
func (mbc *MockBeaconCommittees) Release() {
392396
}

shared/services/rewards/types.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,9 @@ type IntervalDutiesInfo struct {
234234
}
235235

236236
type SlotInfo struct {
237-
Index uint64
238-
Committees map[uint64]*CommitteeInfo
237+
Index uint64
238+
Committees map[uint64]*CommitteeInfo
239+
CommitteeSizes map[uint64]int
239240
}
240241

241242
type CommitteeInfo struct {

0 commit comments

Comments
 (0)