mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-04-19 03:01:06 -04:00
Add PTC assignment support for Duty endpoint (#14032)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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] = ðpb.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(ðpb.BeaconState{
|
||||
state, err := state_native.InitializeFromProtoEpbs(ðpb.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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
1338
proto/prysm/v1alpha1/validator.pb.go
generated
1338
proto/prysm/v1alpha1/validator.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -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"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user