Add PTC assignment support for Duty endpoint (#14032)

This commit is contained in:
Potuz
2024-07-31 13:13:37 -03:00
parent f5754705d5
commit 3f17a44c41
7 changed files with 807 additions and 668 deletions

View File

@@ -213,6 +213,7 @@ type CommitteeAssignment struct {
Committee []primitives.ValidatorIndex
AttesterSlot primitives.Slot
CommitteeIndex primitives.CommitteeIndex
PtcSlot primitives.Slot
}
// verifyAssignmentEpoch verifies if the given epoch is valid for assignment based on the provided state.
@@ -303,6 +304,13 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
vals[v] = struct{}{}
}
assignments := make(map[primitives.ValidatorIndex]*CommitteeAssignment)
activeValidatorCount, err := ActiveValidatorCount(ctx, state, epoch)
if err != nil {
return nil, err
}
ptcPerSlot, PtcMembersPerCommittee := PtcAllocation(activeValidatorCount)
// Compute committee assignments for each slot in the epoch.
for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ {
committees, err := BeaconCommittees(ctx, state, slot)
@@ -321,11 +329,52 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
assignments[vIndex].AttesterSlot = slot
assignments[vIndex].CommitteeIndex = primitives.CommitteeIndex(j)
}
// We only need to assign PTC slots for the first `PTCPerSlot` committees of a given slot.
if uint64(j) < ptcPerSlot {
assignments = PTCAssignments(committee, assignments, PtcMembersPerCommittee, slot)
}
}
}
return assignments, nil
}
// PTCAssignments updates the PTC slot assignments for the given committee members.
// committee: a slice of ValidatorIndex representing committee members.
// assignments: a map of ValidatorIndex to CommitteeAssignment where assignments will be updated.
// membersPerCommittee: the number of members to be assigned to the PTC committee.
// slot: the slot to be assigned for PTC assignment.
// Returns the updated assignments map.
func PTCAssignments(committee []primitives.ValidatorIndex,
assignments map[primitives.ValidatorIndex]*CommitteeAssignment,
membersPerCommittee uint64,
slot primitives.Slot) map[primitives.ValidatorIndex]*CommitteeAssignment {
committeeLength := uint64(len(committee))
// If the number of PTC members is greater than Beacon members,
// return the current assignments without changes.
if membersPerCommittee > committeeLength {
return assignments
}
// Calculate the starting index for PTC committee.
ptcStartIndex := committeeLength - membersPerCommittee
// Loop through the selected committee members for PTC assignments.
for i := ptcStartIndex; i < committeeLength; i++ {
vIndex := committee[i]
assignment, exists := assignments[vIndex]
if !exists {
assignment = &CommitteeAssignment{}
assignments[vIndex] = assignment
}
assignment.PtcSlot = slot
}
return assignments
}
// VerifyBitfieldLength verifies that a bitfield length matches the given committee size.
func VerifyBitfieldLength(bf bitfield.Bitfield, committeeSize uint64) error {
if bf.Len() != committeeSize {

View File

@@ -3,6 +3,7 @@ package helpers_test
import (
"context"
"fmt"
"slices"
"strconv"
"testing"
@@ -10,6 +11,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/container/slice"
@@ -717,15 +719,26 @@ func TestCommitteeIndices(t *testing.T) {
assert.DeepEqual(t, []primitives.CommitteeIndex{0, 1, 3}, indices)
}
func TestAttestationCommittees(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().TargetCommitteeSize))
func TestCommitteeAssignments_PTC(t *testing.T) {
helpers.ClearCache()
// Create 10 committees. Total 40960 validators.
committeeCount := uint64(10)
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize * uint64(params.BeaconConfig().SlotsPerEpoch)
validators := make([]*ethpb.Validator, validatorCount)
validatorIndices := make([]primitives.ValidatorIndex, validatorCount)
for i := 0; i < len(validators); i++ {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = &ethpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: k,
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
validatorIndices[i] = primitives.ValidatorIndex(i)
}
state, err := state_native.InitializeFromProtoPhase0(&ethpb.BeaconState{
state, err := state_native.InitializeFromProtoEpbs(&ethpb.BeaconStateEPBS{
Validators: validators,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
})
@@ -749,6 +762,31 @@ func TestAttestationCommittees(t *testing.T) {
assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[0])))
assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[1])))
})
as, err := helpers.CommitteeAssignments(context.Background(), state, 1, validatorIndices)
require.NoError(t, err)
// Capture all the slots and all the validator index that belonged in a PTC using a map for verification later.
slotValidatorMap := make(map[primitives.Slot][]primitives.ValidatorIndex)
for i, a := range as {
slotValidatorMap[a.PtcSlot] = append(slotValidatorMap[a.PtcSlot], i)
}
// Verify that all the slots have the correct number of PTC.
for s, v := range slotValidatorMap {
if s == 0 {
continue
}
// Make sure all the PTC are the correct size from the map.
require.Equal(t, len(v), field_params.PTCSize)
// Get the actual PTC from the beacon state using the helper function
ptc, err := helpers.GetPayloadTimelinessCommittee(context.Background(), state, s)
require.NoError(t, err)
for _, index := range ptc {
i := slices.Index(v, index)
require.NotEqual(t, -1, i) // PTC not found from the assignment map
}
}
}
func TestBeaconCommittees(t *testing.T) {

View File

@@ -74,8 +74,7 @@ func GetPayloadTimelinessCommittee(ctx context.Context, state state.ReadOnlyBeac
if err != nil {
return nil, errors.Wrap(err, "could not compute active validator count")
}
committeesPerSlot := math.LargestPowerOfTwo(math.Min(SlotCommitteeCount(activeCount), fieldparams.PTCSize))
membersPerCommittee := fieldparams.PTCSize / committeesPerSlot
committeesPerSlot, membersPerCommittee := PtcAllocation(activeCount)
for i := uint64(0); i < committeesPerSlot; i++ {
committee, err := BeaconCommitteeFromState(ctx, state, slot, primitives.CommitteeIndex(i))
if err != nil {
@@ -89,3 +88,13 @@ func GetPayloadTimelinessCommittee(ctx context.Context, state state.ReadOnlyBeac
}
return
}
// PtcAllocation returns:
// 1. The number of beacon committees that PTC will borrow from in a slot.
// 2. The number of validators that PTC will borrow from in a beacon committee.
func PtcAllocation(totalActive uint64) (committeesPerSlot, membersPerCommittee uint64) {
slotCommittees := SlotCommitteeCount(totalActive)
committeesPerSlot = math.LargestPowerOfTwo(math.Min(slotCommittees, fieldparams.PTCSize))
membersPerCommittee = fieldparams.PTCSize / committeesPerSlot
return
}

View File

@@ -91,3 +91,27 @@ func TestGetPayloadTimelinessCommittee(t *testing.T) {
require.DeepEqual(t, committee1[len(committee1)-64:], ptc[:64])
}
func Test_PtcAllocation(t *testing.T) {
tests := []struct {
totalActive uint64
memberPerCommittee uint64
committeesPerSlot uint64
}{
{64, 512, 1},
{params.BeaconConfig().MinGenesisActiveValidatorCount, 128, 4},
{25600, 128, 4},
{256000, 16, 32},
{1024000, 8, 64},
}
for _, test := range tests {
committeesPerSlot, memberPerCommittee := helpers.PtcAllocation(test.totalActive)
if memberPerCommittee != test.memberPerCommittee {
t.Errorf("memberPerCommittee(%d) = %d; expected %d", test.totalActive, memberPerCommittee, test.memberPerCommittee)
}
if committeesPerSlot != test.committeesPerSlot {
t.Errorf("committeesPerSlot(%d) = %d; expected %d", test.totalActive, committeesPerSlot, test.committeesPerSlot)
}
}
}

View File

@@ -108,6 +108,7 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb.
assignment.Committee = ca.Committee
assignment.AttesterSlot = ca.AttesterSlot
assignment.CommitteeIndex = ca.CommitteeIndex
assignment.PtcSlot = ca.PtcSlot
}
// Save the next epoch assignments.
ca, ok = nextEpochAssignments[idx]
@@ -115,6 +116,7 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb.
nextAssignment.Committee = ca.Committee
nextAssignment.AttesterSlot = ca.AttesterSlot
nextAssignment.CommitteeIndex = ca.CommitteeIndex
nextAssignment.PtcSlot = ca.PtcSlot
}
} else {
// If the validator isn't in the beacon state, try finding their deposit to determine their status.

File diff suppressed because it is too large Load Diff

View File

@@ -572,6 +572,9 @@ message DutiesResponse {
// The number of committees in the duty's slot.
uint64 committees_at_slot = 9;
// Slot at which a validator attest to payload attestation timeliness.
uint64 ptc_slot = 10 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Slot"];
}
}