Skip to content

Commit

Permalink
Add PTC assignment support for Duty endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
terencechain committed May 21, 2024
1 parent 84244b6 commit 6e1f2a1
Show file tree
Hide file tree
Showing 7 changed files with 734 additions and 612 deletions.
21 changes: 20 additions & 1 deletion beacon-chain/core/helpers/beacon_committee.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ type CommitteeAssignmentContainer struct {
Committee []primitives.ValidatorIndex
AttesterSlot primitives.Slot
CommitteeIndex primitives.CommitteeIndex
PtcSlot primitives.Slot
}

// CommitteeAssignments is a map of validator indices pointing to the appropriate committee
Expand Down Expand Up @@ -220,6 +221,7 @@ func CommitteeAssignments(
numCommitteesPerSlot := SlotCommitteeCount(uint64(len(activeValidatorIndices)))
validatorIndexToCommittee := make(map[primitives.ValidatorIndex]*CommitteeAssignmentContainer, len(activeValidatorIndices))

committeesPerSlot, membersPerCommittee := PtcAllocation(uint64(len(activeValidatorIndices)))
// Compute all committees for all slots.
for i := primitives.Slot(0); i < params.BeaconConfig().SlotsPerEpoch; i++ {
// Compute committees.
Expand All @@ -235,8 +237,25 @@ func CommitteeAssignments(
CommitteeIndex: primitives.CommitteeIndex(j),
AttesterSlot: slot,
}
for _, vIndex := range committee {

for cIndex, vIndex := range committee {
validatorIndexToCommittee[vIndex] = cac

// Ignore PTC duty if the expected PTC size is greater than the beacon committee size.
committeLength := uint64(len(committee))
if membersPerCommittee >= committeLength {
continue
}
PtcStartIndex := committeLength - membersPerCommittee
if uint64(cIndex) >= PtcStartIndex && j < committeesPerSlot {
// Create a new CommitteeAssignmentContainer to avoid mutating the previous reference if they belong to the same committee.
validatorIndexToCommittee[vIndex] = &CommitteeAssignmentContainer{
Committee: committee,
CommitteeIndex: primitives.CommitteeIndex(j),
AttesterSlot: slot,
PtcSlot: slot,
}
}
}
}
}
Expand Down
51 changes: 51 additions & 0 deletions beacon-chain/core/helpers/beacon_committee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package helpers_test
import (
"context"
"fmt"
"slices"
"strconv"
"testing"

"github.com/prysmaticlabs/go-bitfield"
"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"
Expand Down Expand Up @@ -708,3 +710,52 @@ func TestCommitteeIndices(t *testing.T) {
indices := helpers.CommitteeIndices(bitfield)
assert.DeepEqual(t, []primitives.CommitteeIndex{0, 1, 3}, indices)
}

func TestCommitteeAssignments_PTC(t *testing.T) {
// Create 10 committees. Total 40960 validators.
committeeCount := uint64(10)
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize * uint64(params.BeaconConfig().SlotsPerEpoch)
validators := make([]*ethpb.Validator, validatorCount)

for i := 0; i < len(validators); i++ {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = &ethpb.Validator{
PublicKey: k,
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
}

state, err := state_native.InitializeFromProtoEpbs(&ethpb.BeaconStateEPBS{
Validators: validators,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
})
require.NoError(t, err)

as, _, err := helpers.CommitteeAssignments(context.Background(), state, 1)
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
}
}
}
13 changes: 11 additions & 2 deletions beacon-chain/core/helpers/payload_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
24 changes: 24 additions & 0 deletions beacon-chain/core/helpers/payload_attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
2 changes: 2 additions & 0 deletions beacon-chain/rpc/prysm/v1alpha1/validator/duties.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ 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 = nextCommitteeAssignments[idx]
if ok {
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.
Expand Down
Loading

0 comments on commit 6e1f2a1

Please sign in to comment.