diff --git a/beacon-chain/core/helpers/beacon_committee.go b/beacon-chain/core/helpers/beacon_committee.go index 91d6c5a863ac..6437670ca678 100644 --- a/beacon-chain/core/helpers/beacon_committee.go +++ b/beacon-chain/core/helpers/beacon_committee.go @@ -82,6 +82,47 @@ func AttestationCommittees(ctx context.Context, st state.ReadOnlyBeaconState, at return committees, nil } +// BeaconCommittees returns the list of all beacon committees for a given state at a given slot. +func BeaconCommittees(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot) ([][]primitives.ValidatorIndex, error) { + epoch := slots.ToEpoch(slot) + activeCount, err := ActiveValidatorCount(ctx, state, epoch) + if err != nil { + return nil, errors.Wrap(err, "could not compute active validator count") + } + committeesPerSlot := SlotCommitteeCount(activeCount) + seed, err := Seed(state, epoch, params.BeaconConfig().DomainBeaconAttester) + if err != nil { + return nil, errors.Wrap(err, "could not get seed") + } + + committees := make([][]primitives.ValidatorIndex, committeesPerSlot) + var activeIndices []primitives.ValidatorIndex + + for idx := primitives.CommitteeIndex(0); idx < primitives.CommitteeIndex(len(committees)); idx++ { + committee, err := committeeCache.Committee(ctx, slot, seed, idx) + if err != nil { + return nil, errors.Wrap(err, "could not interface with committee cache") + } + if committee != nil { + committees[idx] = committee + continue + } + + if len(activeIndices) == 0 { + activeIndices, err = ActiveValidatorIndices(ctx, state, epoch) + if err != nil { + return nil, errors.Wrap(err, "could not get active indices") + } + } + committee, err = BeaconCommittee(ctx, activeIndices, seed, slot, idx) + if err != nil { + return nil, errors.Wrap(err, "could not compute beacon committee") + } + committees[idx] = committee + } + return committees, nil +} + // BeaconCommitteeFromState returns the crosslink committee of a given slot and committee index. This // is a spec implementation where state is used as an argument. In case of state retrieval // becomes expensive, consider using BeaconCommittee below. diff --git a/beacon-chain/core/helpers/beacon_committee_test.go b/beacon-chain/core/helpers/beacon_committee_test.go index 2bbafdd0fa9e..9604ede6f596 100644 --- a/beacon-chain/core/helpers/beacon_committee_test.go +++ b/beacon-chain/core/helpers/beacon_committee_test.go @@ -18,6 +18,7 @@ import ( ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" "github.com/prysmaticlabs/prysm/v5/time/slots" ) @@ -749,3 +750,27 @@ func TestAttestationCommittees(t *testing.T) { assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[1]))) }) } + +func TestBeaconCommittees(t *testing.T) { + prevConfig := params.BeaconConfig().Copy() + defer params.OverrideBeaconConfig(prevConfig) + c := params.BeaconConfig().Copy() + c.MinGenesisActiveValidatorCount = 128 + c.SlotsPerEpoch = 4 + c.TargetCommitteeSize = 16 + params.OverrideBeaconConfig(c) + + state, _ := util.DeterministicGenesisState(t, 256) + + activeCount, err := helpers.ActiveValidatorCount(context.Background(), state, 0) + require.NoError(t, err) + committeesPerSlot := helpers.SlotCommitteeCount(activeCount) + committees, err := helpers.BeaconCommittees(context.Background(), state, 0) + require.NoError(t, err) + require.Equal(t, committeesPerSlot, uint64(len(committees))) + for idx := primitives.CommitteeIndex(0); idx < primitives.CommitteeIndex(len(committees)); idx++ { + committee, err := helpers.BeaconCommitteeFromState(context.Background(), state, 0, idx) + require.NoError(t, err) + require.DeepEqual(t, committees[idx], committee) + } +}