diff --git a/beacon-chain/core/helpers/committee.go b/beacon-chain/core/helpers/committee.go index 38adce3ea..e818fff71 100644 --- a/beacon-chain/core/helpers/committee.go +++ b/beacon-chain/core/helpers/committee.go @@ -186,7 +186,9 @@ func CommitteeAssignments( return nil, nil, err } proposerIndexToSlots := make(map[uint64][]uint64, params.BeaconConfig().SlotsPerEpoch) - for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ { + // Proposal epochs do not have a look ahead, so we skip them over here. + validProposalEpoch := epoch < nextEpoch + for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch && validProposalEpoch; slot++ { // Skip proposer assignment for genesis slot. if slot == 0 { continue diff --git a/beacon-chain/core/helpers/committee_test.go b/beacon-chain/core/helpers/committee_test.go index b3cc2122c..9375984d8 100644 --- a/beacon-chain/core/helpers/committee_test.go +++ b/beacon-chain/core/helpers/committee_test.go @@ -203,6 +203,36 @@ func TestCommitteeAssignments_CanRetrieve(t *testing.T) { } } +func TestCommitteeAssignments_CannotRetrieveFuture(t *testing.T) { + // Initialize test with 256 validators, each slot and each index gets 4 validators. + validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch) + for i := 0; i < len(validators); i++ { + // First 2 epochs only half validators are activated. + var activationEpoch uint64 + if i >= len(validators)/2 { + activationEpoch = 3 + } + validators[i] = ðpb.Validator{ + ActivationEpoch: activationEpoch, + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + } + } + + state, err := beaconstate.InitializeFromProto(&pb.BeaconState{ + Validators: validators, + Slot: 2 * params.BeaconConfig().SlotsPerEpoch, // epoch 2 + RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), + }) + require.NoError(t, err) + _, proposerIndxs, err := CommitteeAssignments(state, CurrentEpoch(state)) + require.NoError(t, err) + require.NotEqual(t, 0, len(proposerIndxs), "wanted non-zero proposer index set") + + _, proposerIndxs, err = CommitteeAssignments(state, CurrentEpoch(state)+1) + require.NoError(t, err) + require.Equal(t, 0, len(proposerIndxs), "wanted empty proposer index set") +} + func TestCommitteeAssignments_EverySlotHasMin1Proposer(t *testing.T) { // Initialize test with 256 validators, each slot and each index gets 4 validators. validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch) diff --git a/beacon-chain/core/helpers/validators_test.go b/beacon-chain/core/helpers/validators_test.go index b94dfe2dd..d8b057c2e 100644 --- a/beacon-chain/core/helpers/validators_test.go +++ b/beacon-chain/core/helpers/validators_test.go @@ -275,6 +275,39 @@ func TestBeaconProposerIndex_OK(t *testing.T) { } } +func TestBeaconProposerIndex_BadState(t *testing.T) { + params.SetupTestConfigCleanup(t) + ClearCache() + c := params.BeaconConfig() + c.MinGenesisActiveValidatorCount = 16384 + params.OverrideBeaconConfig(c) + validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount/8) + for i := 0; i < len(validators); i++ { + validators[i] = ðpb.Validator{ + ExitEpoch: params.BeaconConfig().FarFutureEpoch, + } + } + roots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot) + for i := uint64(0); i < params.BeaconConfig().SlotsPerHistoricalRoot; i++ { + roots[i] = make([]byte, 32) + } + + state, err := beaconstate.InitializeFromProto(&pb.BeaconState{ + Validators: validators, + Slot: 0, + RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), + BlockRoots: roots, + StateRoots: roots, + }) + require.NoError(t, err) + // Set a very high slot, so that retrieved block root will be + // non existent for the proposer cache. + require.NoError(t, state.SetSlot(100)) + _, err = BeaconProposerIndex(state) + require.NoError(t, err) + assert.Equal(t, 0, len(proposerIndicesCache.ProposerIndicesCache.ListKeys())) +} + func TestComputeProposerIndex_Compatibility(t *testing.T) { validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount) for i := 0; i < len(validators); i++ { diff --git a/beacon-chain/rpc/validator/assignments.go b/beacon-chain/rpc/validator/assignments.go index 0fdfaaadd..4bcf92fea 100644 --- a/beacon-chain/rpc/validator/assignments.go +++ b/beacon-chain/rpc/validator/assignments.go @@ -128,7 +128,7 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb. return nil, status.Errorf(codes.Internal, "Could not compute committee assignments: %v", err) } // Query the next epoch assignments for committee subnet subscriptions. - nextCommitteeAssignments, nextProposerIndexToSlots, err := helpers.CommitteeAssignments(s, req.Epoch+1) + nextCommitteeAssignments, _, err := helpers.CommitteeAssignments(s, req.Epoch+1) if err != nil { return nil, status.Errorf(codes.Internal, "Could not compute next committee assignments: %v", err) } @@ -153,9 +153,9 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb. assignment.Status = assignmentStatus(s, idx) assignment.ProposerSlots = proposerIndexToSlots[idx] + // The next epoch has no lookup for proposer indexes. nextAssignment.ValidatorIndex = idx nextAssignment.Status = assignmentStatus(s, idx) - nextAssignment.ProposerSlots = nextProposerIndexToSlots[idx] ca, ok := committeeAssignments[idx] if ok {