From d4f8c2ae6362e102f153040417f3ee843e8b712d Mon Sep 17 00:00:00 2001 From: terence tsao Date: Wed, 22 May 2024 07:24:13 -0700 Subject: [PATCH 1/2] Rewrite CommitteeAssignments to not return every validator --- beacon-chain/core/helpers/beacon_committee.go | 137 +++++++++++------- .../core/helpers/beacon_committee_test.go | 45 +++--- beacon-chain/rpc/eth/validator/handlers.go | 12 +- .../rpc/eth/validator/handlers_test.go | 1 + .../rpc/prysm/v1alpha1/beacon/assignments.go | 17 ++- .../prysm/v1alpha1/beacon/assignments_test.go | 48 +++--- .../rpc/prysm/v1alpha1/validator/duties.go | 30 +++- 7 files changed, 180 insertions(+), 110 deletions(-) diff --git a/beacon-chain/core/helpers/beacon_committee.go b/beacon-chain/core/helpers/beacon_committee.go index d1371a953779..ae4c7c1c4222 100644 --- a/beacon-chain/core/helpers/beacon_committee.go +++ b/beacon-chain/core/helpers/beacon_committee.go @@ -143,105 +143,138 @@ func BeaconCommittee( return ComputeCommittee(validatorIndices, seed, indexOffset, count) } -// CommitteeAssignmentContainer represents a committee list, committee index, and to be attested slot for a given epoch. -type CommitteeAssignmentContainer struct { +// CommitteeAssignment represents committee list, committee index, and to be attested slot for a given epoch. +type CommitteeAssignment struct { Committee []primitives.ValidatorIndex AttesterSlot primitives.Slot CommitteeIndex primitives.CommitteeIndex } -// CommitteeAssignments is a map of validator indices pointing to the appropriate committee -// assignment for the given epoch. -// -// 1. Determine the proposer validator index for each slot. -// 2. Compute all committees. -// 3. Determine the attesting slot for each committee. -// 4. Construct a map of validator indices pointing to the respective committees. -func CommitteeAssignments( - ctx context.Context, - state state.BeaconState, - epoch primitives.Epoch, -) (map[primitives.ValidatorIndex]*CommitteeAssignmentContainer, map[primitives.ValidatorIndex][]primitives.Slot, error) { +// verifyAssignmentEpoch verifies if the given epoch is valid for assignment based on the provided state. +// It checks if the epoch is not greater than the next epoch, and if the start slot of the epoch is greater +// than or equal to the minimum valid start slot calculated based on the state's current slot and historical roots. +func verifyAssignmentEpoch(epoch primitives.Epoch, state state.BeaconState) error { nextEpoch := time.NextEpoch(state) if epoch > nextEpoch { - return nil, nil, fmt.Errorf( - "epoch %d can't be greater than next epoch %d", - epoch, - nextEpoch, - ) + return fmt.Errorf("epoch %d can't be greater than next epoch %d", epoch, nextEpoch) } - // We determine the slots in which proposers are supposed to act. - // Some validators may need to propose multiple times per epoch, so - // we use a map of proposer idx -> []slot to keep track of this possibility. startSlot, err := slots.EpochStart(epoch) if err != nil { - return nil, nil, err + return err } minValidStartSlot := primitives.Slot(0) - if state.Slot() >= params.BeaconConfig().SlotsPerHistoricalRoot { - minValidStartSlot = state.Slot() - params.BeaconConfig().SlotsPerHistoricalRoot + if stateSlot := state.Slot(); stateSlot >= params.BeaconConfig().SlotsPerHistoricalRoot { + minValidStartSlot = stateSlot - params.BeaconConfig().SlotsPerHistoricalRoot } if startSlot < minValidStartSlot { - return nil, nil, fmt.Errorf("start slot %d is smaller than the minimum valid start slot %d", startSlot, minValidStartSlot) + return fmt.Errorf("start slot %d is smaller than the minimum valid start slot %d", startSlot, minValidStartSlot) + } + return nil +} + +// ProposerAssignments calculates proposer assignments for each validator during the specified epoch. +// It verifies the validity of the epoch, then iterates through each slot in the epoch to determine the +// proposer for that slot and assigns them accordingly. +func ProposerAssignments(ctx context.Context, state state.BeaconState, epoch primitives.Epoch) (map[primitives.ValidatorIndex][]primitives.Slot, error) { + // Verify if the epoch is valid for assignment based on the provided state. + if err := verifyAssignmentEpoch(epoch, state); err != nil { + return nil, err + } + startSlot, err := slots.EpochStart(epoch) + if err != nil { + return nil, err } - proposerIndexToSlots := make(map[primitives.ValidatorIndex][]primitives.Slot, params.BeaconConfig().SlotsPerEpoch) + proposerAssignments := make(map[primitives.ValidatorIndex][]primitives.Slot) + + originalStateSlot := state.Slot() + for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ { // Skip proposer assignment for genesis slot. if slot == 0 { continue } + // Set the state's current slot. if err := state.SetSlot(slot); err != nil { - return nil, nil, err + return nil, err } + + // Determine the proposer index for the current slot. i, err := BeaconProposerIndex(ctx, state) if err != nil { - return nil, nil, errors.Wrapf(err, "could not check proposer at slot %d", state.Slot()) + return nil, errors.Wrapf(err, "could not check proposer at slot %d", state.Slot()) } - proposerIndexToSlots[i] = append(proposerIndexToSlots[i], slot) - } - // If previous proposer indices computation is outside if current proposal epoch range, - // we need to reset state slot back to start slot so that we can compute the correct committees. - currentProposalEpoch := epoch < nextEpoch - if !currentProposalEpoch { - if err := state.SetSlot(state.Slot() - params.BeaconConfig().SlotsPerEpoch); err != nil { - return nil, nil, err + // Append the slot to the proposer's assignments. + if _, ok := proposerAssignments[i]; !ok { + proposerAssignments[i] = make([]primitives.Slot, 0) } + proposerAssignments[i] = append(proposerAssignments[i], slot) } + // Reset state back to its original slot. + if err := state.SetSlot(originalStateSlot); err != nil { + return nil, err + } + + return proposerAssignments, nil +} + +// CommitteeAssignments calculates committee assignments for each validator during the specified epoch. +// It retrieves active validator indices, determines the number of committees per slot, and computes +// assignments for each validator based on their presence in the provided validators slice. +func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch primitives.Epoch, validators []primitives.ValidatorIndex) (map[primitives.ValidatorIndex]*CommitteeAssignment, error) { + // Verify if the epoch is valid for assignment based on the provided state. + if err := verifyAssignmentEpoch(epoch, state); err != nil { + return nil, err + } + + // Retrieve active validator indices for the specified epoch. activeValidatorIndices, err := ActiveValidatorIndices(ctx, state, epoch) if err != nil { - return nil, nil, err + return nil, err } - // Each slot in an epoch has a different set of committees. This value is derived from the - // active validator set, which does not change. + + // Determine the number of committees per slot based on the number of active validator indices. numCommitteesPerSlot := SlotCommitteeCount(uint64(len(activeValidatorIndices))) - validatorIndexToCommittee := make(map[primitives.ValidatorIndex]*CommitteeAssignmentContainer, len(activeValidatorIndices)) - // Compute all committees for all slots. + startSlot, err := slots.EpochStart(epoch) + if err != nil { + return nil, err + } + + assignments := make(map[primitives.ValidatorIndex]*CommitteeAssignment) + vals := make(map[primitives.ValidatorIndex]struct{}) + for _, v := range validators { + vals[v] = struct{}{} + } + + // Compute committee assignments for each slot in the epoch. for i := primitives.Slot(0); i < params.BeaconConfig().SlotsPerEpoch; i++ { - // Compute committees. + // Compute committees for the current slot. for j := uint64(0); j < numCommitteesPerSlot; j++ { slot := startSlot + i - committee, err := BeaconCommitteeFromState(ctx, state, slot, primitives.CommitteeIndex(j) /*committee index*/) + committee, err := BeaconCommitteeFromState(ctx, state, slot, primitives.CommitteeIndex(j)) if err != nil { - return nil, nil, err + return nil, err } - cac := &CommitteeAssignmentContainer{ - Committee: committee, - CommitteeIndex: primitives.CommitteeIndex(j), - AttesterSlot: slot, - } for _, vIndex := range committee { - validatorIndexToCommittee[vIndex] = cac + if _, ok := vals[vIndex]; !ok { // Skip if the validator is not in the provided validators slice. + continue + } + if _, ok := assignments[vIndex]; !ok { + assignments[vIndex] = &CommitteeAssignment{} + } + assignments[vIndex].Committee = committee + assignments[vIndex].AttesterSlot = slot + assignments[vIndex].CommitteeIndex = primitives.CommitteeIndex(j) } } } - return validatorIndexToCommittee, proposerIndexToSlots, nil + return assignments, nil } // VerifyBitfieldLength verifies that a bitfield length matches the given committee size. diff --git a/beacon-chain/core/helpers/beacon_committee_test.go b/beacon-chain/core/helpers/beacon_committee_test.go index 1402c207394d..fd3cc255230e 100644 --- a/beacon-chain/core/helpers/beacon_committee_test.go +++ b/beacon-chain/core/helpers/beacon_committee_test.go @@ -104,7 +104,10 @@ func TestCommitteeAssignments_CannotRetrieveFutureEpoch(t *testing.T) { Slot: 0, // Epoch 0. }) require.NoError(t, err) - _, _, err = helpers.CommitteeAssignments(context.Background(), state, epoch+1) + _, err = helpers.CommitteeAssignments(context.Background(), state, epoch+1, nil) + assert.ErrorContains(t, "can't be greater than next epoch", err) + + _, err = helpers.ProposerAssignments(context.Background(), state, epoch+1) assert.ErrorContains(t, "can't be greater than next epoch", err) } @@ -128,10 +131,10 @@ func TestCommitteeAssignments_NoProposerForSlot0(t *testing.T) { RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), }) require.NoError(t, err) - _, proposerIndexToSlots, err := helpers.CommitteeAssignments(context.Background(), state, 0) - require.NoError(t, err, "Failed to determine CommitteeAssignments") - for _, ss := range proposerIndexToSlots { - for _, s := range ss { + assignments, err := helpers.ProposerAssignments(context.Background(), state, 0) + require.NoError(t, err, "Failed to determine Assignments") + for _, slots := range assignments { + for _, s := range slots { assert.NotEqual(t, uint64(0), s, "No proposer should be assigned to slot 0") } } @@ -140,6 +143,7 @@ func TestCommitteeAssignments_NoProposerForSlot0(t *testing.T) { func TestCommitteeAssignments_CanRetrieve(t *testing.T) { // Initialize test with 256 validators, each slot and each index gets 4 validators. validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch) + validatorIndices := make([]primitives.ValidatorIndex, len(validators)) for i := 0; i < len(validators); i++ { // First 2 epochs only half validators are activated. var activationEpoch primitives.Epoch @@ -150,6 +154,7 @@ func TestCommitteeAssignments_CanRetrieve(t *testing.T) { ActivationEpoch: activationEpoch, ExitEpoch: params.BeaconConfig().FarFutureEpoch, } + validatorIndices[i] = primitives.ValidatorIndex(i) } state, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{ @@ -201,14 +206,16 @@ func TestCommitteeAssignments_CanRetrieve(t *testing.T) { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { helpers.ClearCache() - validatorIndexToCommittee, proposerIndexToSlots, err := helpers.CommitteeAssignments(context.Background(), state, slots.ToEpoch(tt.slot)) - require.NoError(t, err, "Failed to determine CommitteeAssignments") - cac := validatorIndexToCommittee[tt.index] + assignments, err := helpers.CommitteeAssignments(context.Background(), state, slots.ToEpoch(tt.slot), validatorIndices) + require.NoError(t, err, "Failed to determine Assignments") + cac := assignments[tt.index] assert.Equal(t, tt.committeeIndex, cac.CommitteeIndex, "Unexpected committeeIndex for validator index %d", tt.index) assert.Equal(t, tt.slot, cac.AttesterSlot, "Unexpected slot for validator index %d", tt.index) - if len(proposerIndexToSlots[tt.index]) > 0 && proposerIndexToSlots[tt.index][0] != tt.proposerSlot { + proposerAssignments, err := helpers.ProposerAssignments(context.Background(), state, slots.ToEpoch(tt.slot)) + require.NoError(t, err) + if len(proposerAssignments[tt.index]) > 0 && proposerAssignments[tt.index][0] != tt.proposerSlot { t.Errorf("wanted proposer slot %d, got proposer slot %d for validator index %d", - tt.proposerSlot, proposerIndexToSlots[tt.index][0], tt.index) + tt.proposerSlot, proposerAssignments[tt.index][0], tt.index) } assert.DeepEqual(t, tt.committee, cac.Committee, "Unexpected committee for validator index %d", tt.index) }) @@ -238,13 +245,13 @@ func TestCommitteeAssignments_CannotRetrieveFuture(t *testing.T) { RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), }) require.NoError(t, err) - _, proposerIndxs, err := helpers.CommitteeAssignments(context.Background(), state, time.CurrentEpoch(state)) + assignments, err := helpers.ProposerAssignments(context.Background(), state, time.CurrentEpoch(state)) require.NoError(t, err) - require.NotEqual(t, 0, len(proposerIndxs), "wanted non-zero proposer index set") + require.NotEqual(t, 0, len(assignments), "wanted non-zero proposer index set") - _, proposerIndxs, err = helpers.CommitteeAssignments(context.Background(), state, time.CurrentEpoch(state)+1) + assignments, err = helpers.ProposerAssignments(context.Background(), state, time.CurrentEpoch(state)+1) require.NoError(t, err) - require.NotEqual(t, 0, len(proposerIndxs), "wanted non-zero proposer index set") + require.NotEqual(t, 0, len(assignments), "wanted non-zero proposer index set") } func TestCommitteeAssignments_CannotRetrieveOlderThanSlotsPerHistoricalRoot(t *testing.T) { @@ -264,7 +271,7 @@ func TestCommitteeAssignments_CannotRetrieveOlderThanSlotsPerHistoricalRoot(t *t RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), }) require.NoError(t, err) - _, _, err = helpers.CommitteeAssignments(context.Background(), state, 0) + _, err = helpers.CommitteeAssignments(context.Background(), state, 0, nil) require.ErrorContains(t, "start slot 0 is smaller than the minimum valid start slot 1", err) } @@ -286,12 +293,12 @@ func TestCommitteeAssignments_EverySlotHasMin1Proposer(t *testing.T) { }) require.NoError(t, err) epoch := primitives.Epoch(1) - _, proposerIndexToSlots, err := helpers.CommitteeAssignments(context.Background(), state, epoch) - require.NoError(t, err, "Failed to determine CommitteeAssignments") + assignments, err := helpers.ProposerAssignments(context.Background(), state, epoch) + require.NoError(t, err, "Failed to determine Assignments") slotsWithProposers := make(map[primitives.Slot]bool) - for _, proposerSlots := range proposerIndexToSlots { - for _, slot := range proposerSlots { + for _, slots := range assignments { + for _, slot := range slots { slotsWithProposers[slot] = true } } diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go index 8359ac5656cf..f10dbb9d2b78 100644 --- a/beacon-chain/rpc/eth/validator/handlers.go +++ b/beacon-chain/rpc/eth/validator/handlers.go @@ -664,7 +664,7 @@ func (s *Server) GetAttesterDuties(w http.ResponseWriter, r *http.Request) { return } - committeeAssignments, _, err := helpers.CommitteeAssignments(ctx, st, requestedEpoch) + assignments, err := helpers.CommitteeAssignments(ctx, st, requestedEpoch, requestedValIndices) if err != nil { httputil.HandleError(w, "Could not compute committee assignments: "+err.Error(), http.StatusInternalServerError) return @@ -684,7 +684,7 @@ func (s *Server) GetAttesterDuties(w http.ResponseWriter, r *http.Request) { httputil.HandleError(w, fmt.Sprintf("Invalid validator index %d", index), http.StatusBadRequest) return } - committee := committeeAssignments[index] + committee := assignments[index] if committee == nil { continue } @@ -793,11 +793,11 @@ func (s *Server) GetProposerDuties(w http.ResponseWriter, r *http.Request) { } } - var proposals map[primitives.ValidatorIndex][]primitives.Slot + var assignments map[primitives.ValidatorIndex][]primitives.Slot if nextEpochLookahead { - _, proposals, err = helpers.CommitteeAssignments(ctx, st, nextEpoch) + assignments, err = helpers.ProposerAssignments(ctx, st, nextEpoch) } else { - _, proposals, err = helpers.CommitteeAssignments(ctx, st, requestedEpoch) + assignments, err = helpers.ProposerAssignments(ctx, st, requestedEpoch) } if err != nil { httputil.HandleError(w, "Could not compute committee assignments: "+err.Error(), http.StatusInternalServerError) @@ -805,7 +805,7 @@ func (s *Server) GetProposerDuties(w http.ResponseWriter, r *http.Request) { } duties := make([]*structs.ProposerDuty, 0) - for index, proposalSlots := range proposals { + for index, proposalSlots := range assignments { val, err := st.ValidatorAtIndexReadOnly(index) if err != nil { httputil.HandleError(w, fmt.Sprintf("Could not get validator at index %d: %v", index, err), http.StatusInternalServerError) diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go index 166d2f404212..57209ee7b58e 100644 --- a/beacon-chain/rpc/eth/validator/handlers_test.go +++ b/beacon-chain/rpc/eth/validator/handlers_test.go @@ -1737,6 +1737,7 @@ func TestGetProposerDuties(t *testing.T) { t.Run("next epoch", func(t *testing.T) { bs, err := transition.GenesisBeaconState(context.Background(), deposits, 0, eth1Data) require.NoError(t, err, "Could not set up genesis state") + require.NoError(t, bs.SetSlot(params.BeaconConfig().SlotsPerEpoch)) require.NoError(t, bs.SetBlockRoots(roots)) chainSlot := primitives.Slot(0) chain := &mockChain.ChainService{ diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go index 9d01a5696555..26ff052b4458 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments.go @@ -105,23 +105,28 @@ func (bs *Server) ListValidatorAssignments( } // Initialize all committee related data. - committeeAssignments, proposerIndexToSlots, err := helpers.CommitteeAssignments(ctx, requestedState, requestedEpoch) + assignments, err := helpers.CommitteeAssignments(ctx, requestedState, requestedEpoch, filteredIndices[start:end]) if err != nil { return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err) } + proposalSlots, err := helpers.ProposerAssignments(ctx, requestedState, requestedEpoch) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not compute proposer slots: %v", err) + } + for _, index := range filteredIndices[start:end] { if uint64(index) >= uint64(requestedState.NumValidators()) { return nil, status.Errorf(codes.OutOfRange, "Validator index %d >= validator count %d", index, requestedState.NumValidators()) } - comAssignment := committeeAssignments[index] + a := assignments[index] pubkey := requestedState.PubkeyAtIndex(index) assign := ðpb.ValidatorAssignments_CommitteeAssignment{ - BeaconCommittees: comAssignment.Committee, - CommitteeIndex: comAssignment.CommitteeIndex, - AttesterSlot: comAssignment.AttesterSlot, - ProposerSlots: proposerIndexToSlots[index], + BeaconCommittees: a.Committee, + CommitteeIndex: a.CommitteeIndex, + AttesterSlot: a.AttesterSlot, + ProposerSlots: proposalSlots[index], PublicKey: pubkey[:], ValidatorIndex: index, } diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go index 665a5394a42b..3536c44732ee 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go @@ -174,16 +174,18 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_NoArchive(t *testing. activeIndices, err := helpers.ActiveValidatorIndices(ctx, s, 0) require.NoError(t, err) - committeeAssignments, proposerIndexToSlots, err := helpers.CommitteeAssignments(context.Background(), s, 0) + assignments, err := helpers.CommitteeAssignments(context.Background(), s, 0, activeIndices[0:params.BeaconConfig().DefaultPageSize]) + require.NoError(t, err) + proposerSlots, err := helpers.ProposerAssignments(ctx, s, 0) require.NoError(t, err) for _, index := range activeIndices[0:params.BeaconConfig().DefaultPageSize] { val, err := s.ValidatorAtIndex(index) require.NoError(t, err) wanted = append(wanted, ðpb.ValidatorAssignments_CommitteeAssignment{ - BeaconCommittees: committeeAssignments[index].Committee, - CommitteeIndex: committeeAssignments[index].CommitteeIndex, - AttesterSlot: committeeAssignments[index].AttesterSlot, - ProposerSlots: proposerIndexToSlots[index], + BeaconCommittees: assignments[index].Committee, + CommitteeIndex: assignments[index].CommitteeIndex, + AttesterSlot: assignments[index].AttesterSlot, + ProposerSlots: proposerSlots[index], PublicKey: val.PublicKey, ValidatorIndex: index, }) @@ -244,16 +246,18 @@ func TestServer_ListAssignments_FilterPubkeysIndices_NoPagination(t *testing.T) activeIndices, err := helpers.ActiveValidatorIndices(ctx, s, 0) require.NoError(t, err) - committeeAssignments, proposerIndexToSlots, err := helpers.CommitteeAssignments(context.Background(), s, 0) + assignments, err := helpers.CommitteeAssignments(context.Background(), s, 0, activeIndices[1:4]) + require.NoError(t, err) + proposerSlots, err := helpers.ProposerAssignments(ctx, s, 0) require.NoError(t, err) for _, index := range activeIndices[1:4] { val, err := s.ValidatorAtIndex(index) require.NoError(t, err) wanted = append(wanted, ðpb.ValidatorAssignments_CommitteeAssignment{ - BeaconCommittees: committeeAssignments[index].Committee, - CommitteeIndex: committeeAssignments[index].CommitteeIndex, - AttesterSlot: committeeAssignments[index].AttesterSlot, - ProposerSlots: proposerIndexToSlots[index], + BeaconCommittees: assignments[index].Committee, + CommitteeIndex: assignments[index].CommitteeIndex, + AttesterSlot: assignments[index].AttesterSlot, + ProposerSlots: proposerSlots[index], PublicKey: val.PublicKey, ValidatorIndex: index, }) @@ -312,16 +316,18 @@ func TestServer_ListAssignments_CanFilterPubkeysIndices_WithPagination(t *testin activeIndices, err := helpers.ActiveValidatorIndices(ctx, s, 0) require.NoError(t, err) - committeeAssignments, proposerIndexToSlots, err := helpers.CommitteeAssignments(context.Background(), s, 0) + as, err := helpers.CommitteeAssignments(context.Background(), s, 0, activeIndices[3:5]) + require.NoError(t, err) + proposalSlots, err := helpers.ProposerAssignments(ctx, s, 0) require.NoError(t, err) for _, index := range activeIndices[3:5] { val, err := s.ValidatorAtIndex(index) require.NoError(t, err) assignments = append(assignments, ðpb.ValidatorAssignments_CommitteeAssignment{ - BeaconCommittees: committeeAssignments[index].Committee, - CommitteeIndex: committeeAssignments[index].CommitteeIndex, - AttesterSlot: committeeAssignments[index].AttesterSlot, - ProposerSlots: proposerIndexToSlots[index], + BeaconCommittees: as[index].Committee, + CommitteeIndex: as[index].CommitteeIndex, + AttesterSlot: as[index].AttesterSlot, + ProposerSlots: proposalSlots[index], PublicKey: val.PublicKey, ValidatorIndex: index, }) @@ -340,16 +346,18 @@ func TestServer_ListAssignments_CanFilterPubkeysIndices_WithPagination(t *testin req = ðpb.ListValidatorAssignmentsRequest{Indices: []primitives.ValidatorIndex{1, 2, 3, 4, 5, 6}, PageSize: 5, PageToken: "1"} res, err = bs.ListValidatorAssignments(context.Background(), req) require.NoError(t, err) - cAssignments, proposerIndexToSlots, err := helpers.CommitteeAssignments(context.Background(), s, 0) + as, err = helpers.CommitteeAssignments(context.Background(), s, 0, activeIndices[6:7]) + require.NoError(t, err) + proposalSlots, err = helpers.ProposerAssignments(ctx, s, 0) require.NoError(t, err) for _, index := range activeIndices[6:7] { val, err := s.ValidatorAtIndex(index) require.NoError(t, err) assignments = append(assignments, ðpb.ValidatorAssignments_CommitteeAssignment{ - BeaconCommittees: cAssignments[index].Committee, - CommitteeIndex: cAssignments[index].CommitteeIndex, - AttesterSlot: cAssignments[index].AttesterSlot, - ProposerSlots: proposerIndexToSlots[index], + BeaconCommittees: as[index].Committee, + CommitteeIndex: as[index].CommitteeIndex, + AttesterSlot: as[index].AttesterSlot, + ProposerSlots: proposalSlots[index], PublicKey: val.PublicKey, ValidatorIndex: index, }) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/duties.go b/beacon-chain/rpc/prysm/v1alpha1/validator/duties.go index a34ce85d3eb0..4d4a6ad53b12 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/duties.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/duties.go @@ -7,6 +7,7 @@ import ( coreTime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/time/slots" @@ -52,16 +53,31 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb. return nil, status.Errorf(codes.Internal, "Could not process slots up to %d: %v", epochStartSlot, err) } } - committeeAssignments, proposerIndexToSlots, err := helpers.CommitteeAssignments(ctx, s, req.Epoch) + + requestIndices := make([]primitives.ValidatorIndex, 0, len(req.PublicKeys)) + for _, pubKey := range req.PublicKeys { + idx, ok := s.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) + if !ok { + continue + } + requestIndices = append(requestIndices, idx) + } + + assignments, err := helpers.CommitteeAssignments(ctx, s, req.Epoch, requestIndices) if err != nil { return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err) } // Query the next epoch assignments for committee subnet subscriptions. - nextCommitteeAssignments, _, err := helpers.CommitteeAssignments(ctx, s, req.Epoch+1) + nextEpochAssignments, err := helpers.CommitteeAssignments(ctx, s, req.Epoch+1, requestIndices) if err != nil { return nil, status.Errorf(codes.Internal, "Could not compute next committee assignments: %v", err) } + proposalSlots, err := helpers.ProposerAssignments(ctx, s, req.Epoch) + if err != nil { + return nil, status.Errorf(codes.Internal, "Could not compute proposer slots: %v", err) + } + validatorAssignments := make([]*ethpb.DutiesResponse_Duty, 0, len(req.PublicKeys)) nextValidatorAssignments := make([]*ethpb.DutiesResponse_Duty, 0, len(req.PublicKeys)) @@ -81,20 +97,20 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb. assignment.ValidatorIndex = idx assignment.Status = s - assignment.ProposerSlots = proposerIndexToSlots[idx] + assignment.ProposerSlots = proposalSlots[idx] // The next epoch has no lookup for proposer indexes. nextAssignment.ValidatorIndex = idx nextAssignment.Status = s - ca, ok := committeeAssignments[idx] + ca, ok := assignments[idx] if ok { assignment.Committee = ca.Committee assignment.AttesterSlot = ca.AttesterSlot assignment.CommitteeIndex = ca.CommitteeIndex } // Save the next epoch assignments. - ca, ok = nextCommitteeAssignments[idx] + ca, ok = nextEpochAssignments[idx] if ok { nextAssignment.Committee = ca.Committee nextAssignment.AttesterSlot = ca.AttesterSlot @@ -123,9 +139,9 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb. // Next epoch sync committee duty is assigned with next period sync committee only during // sync period epoch boundary (ie. EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 1). Else wise // next epoch sync committee duty is the same as current epoch. - nextSlotToEpoch := slots.ToEpoch(s.Slot() + 1) + nextEpoch := req.Epoch + 1 currentEpoch := coreTime.CurrentEpoch(s) - if slots.SyncCommitteePeriod(nextSlotToEpoch) == slots.SyncCommitteePeriod(currentEpoch)+1 { + if slots.SyncCommitteePeriod(nextEpoch) > slots.SyncCommitteePeriod(currentEpoch) { nextAssignment.IsSyncCommittee, err = helpers.IsNextPeriodSyncCommittee(s, idx) if err != nil { return nil, status.Errorf(codes.Internal, "Could not determine next epoch sync committee: %v", err) From d220d4d4f5638a9c62b49bfd28f8e3b1d23d49e2 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 23 May 2024 13:21:40 -0700 Subject: [PATCH 2/2] Potuz's feedback --- beacon-chain/core/helpers/beacon_committee.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/beacon-chain/core/helpers/beacon_committee.go b/beacon-chain/core/helpers/beacon_committee.go index ae4c7c1c4222..2980c51091ab 100644 --- a/beacon-chain/core/helpers/beacon_committee.go +++ b/beacon-chain/core/helpers/beacon_committee.go @@ -230,14 +230,14 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr return nil, err } - // Retrieve active validator indices for the specified epoch. - activeValidatorIndices, err := ActiveValidatorIndices(ctx, state, epoch) + // Retrieve active validator count for the specified epoch. + activeValidatorCount, err := ActiveValidatorCount(ctx, state, epoch) if err != nil { return nil, err } // Determine the number of committees per slot based on the number of active validator indices. - numCommitteesPerSlot := SlotCommitteeCount(uint64(len(activeValidatorIndices))) + numCommitteesPerSlot := SlotCommitteeCount(activeValidatorCount) startSlot, err := slots.EpochStart(epoch) if err != nil { @@ -251,10 +251,9 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr } // Compute committee assignments for each slot in the epoch. - for i := primitives.Slot(0); i < params.BeaconConfig().SlotsPerEpoch; i++ { + for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ { // Compute committees for the current slot. for j := uint64(0); j < numCommitteesPerSlot; j++ { - slot := startSlot + i committee, err := BeaconCommitteeFromState(ctx, state, slot, primitives.CommitteeIndex(j)) if err != nil { return nil, err