Skip to content

Commit

Permalink
Refactor churn limit helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
terencechain committed Sep 29, 2023
1 parent fb19ee8 commit fb91eb9
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 46 deletions.
10 changes: 2 additions & 8 deletions beacon-chain/core/epoch/epoch_processing.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,10 @@ func ProcessRegistryUpdates(ctx context.Context, state state.BeaconState) (state
return nil, errors.Wrap(err, "could not get active validator count")
}

churnLimit, err := helpers.ValidatorChurnLimit(activeValidatorCount)
if err != nil {
return nil, errors.Wrap(err, "could not get churn limit")
}
churnLimit := helpers.ValidatorActivationChurnLimit(activeValidatorCount)

if state.Version() >= version.Deneb {
// Cap churn limit to max per epoch churn limit. New in EIP7514.
if churnLimit > params.BeaconConfig().MaxPerEpochActivationChurnLimit {
churnLimit = params.BeaconConfig().MaxPerEpochActivationChurnLimit
}
churnLimit = helpers.ValidatorActivationChurnLimitDeneb(activeValidatorCount)
}

// Prevent churn limit cause index out of bound.
Expand Down
3 changes: 1 addition & 2 deletions beacon-chain/core/epoch/epoch_processing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,7 @@ func TestProcessRegistryUpdates_EligibleToActivate(t *testing.T) {
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
FinalizedCheckpoint: &ethpb.Checkpoint{Epoch: 6, Root: make([]byte, fieldparams.RootLength)},
}
limit, err := helpers.ValidatorChurnLimit(0)
require.NoError(t, err)
limit := helpers.ValidatorActivationChurnLimit(0)
for i := uint64(0); i < limit+10; i++ {
base.Validators = append(base.Validators, &ethpb.Validator{
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
Expand Down
31 changes: 24 additions & 7 deletions beacon-chain/core/helpers/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,23 +206,40 @@ func ActivationExitEpoch(epoch primitives.Epoch) primitives.Epoch {
return epoch + 1 + params.BeaconConfig().MaxSeedLookahead
}

// ValidatorChurnLimit returns the number of validators that are allowed to
// enter and exit validator pool for an epoch.
//
// Spec pseudocode definition:
// calculateChurnLimit based on the formula in the spec.
//
// def get_validator_churn_limit(state: BeaconState) -> uint64:
// """
// Return the validator churn limit for the current epoch.
// """
// active_validator_indices = get_active_validator_indices(state, get_current_epoch(state))
// return max(MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // CHURN_LIMIT_QUOTIENT)
func ValidatorChurnLimit(activeValidatorCount uint64) (uint64, error) {
func calculateChurnLimit(activeValidatorCount uint64) uint64 {
churnLimit := activeValidatorCount / params.BeaconConfig().ChurnLimitQuotient
if churnLimit < params.BeaconConfig().MinPerEpochChurnLimit {
churnLimit = params.BeaconConfig().MinPerEpochChurnLimit
return params.BeaconConfig().MinPerEpochChurnLimit
}
return churnLimit
}

// ValidatorActivationChurnLimit returns the maximum number of validators that can be activated in a slot.
func ValidatorActivationChurnLimit(activeValidatorCount uint64) uint64 {
return calculateChurnLimit(activeValidatorCount)
}

// ValidatorExitChurnLimit returns the maximum number of validators that can be exited in a slot.
func ValidatorExitChurnLimit(activeValidatorCount uint64) uint64 {
return calculateChurnLimit(activeValidatorCount)
}

// ValidatorActivationChurnLimitDeneb returns the maximum number of validators that can be activated in a slot post Deneb.
func ValidatorActivationChurnLimitDeneb(activeValidatorCount uint64) uint64 {
limit := calculateChurnLimit(activeValidatorCount)
// New in Deneb.
if limit > params.BeaconConfig().MaxPerEpochActivationChurnLimit {
return params.BeaconConfig().MaxPerEpochActivationChurnLimit
}
return churnLimit, nil
return limit
}

// BeaconProposerIndex returns proposer index of a current slot.
Expand Down
45 changes: 43 additions & 2 deletions beacon-chain/core/helpers/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,50 @@ func TestChurnLimit_OK(t *testing.T) {
require.NoError(t, err)
validatorCount, err := ActiveValidatorCount(context.Background(), beaconState, time.CurrentEpoch(beaconState))
require.NoError(t, err)
resultChurn, err := ValidatorChurnLimit(validatorCount)
resultChurn := ValidatorActivationChurnLimit(validatorCount)
assert.Equal(t, test.wantedChurn, resultChurn, "ValidatorActivationChurnLimit(%d)", test.validatorCount)
}
}

func TestChurnLimitDeneb_OK(t *testing.T) {
tests := []struct {
validatorCount int
wantedChurn uint64
}{
{1000, 4},
{100000, 4},
{1000000, params.BeaconConfig().MaxPerEpochActivationChurnLimit},
{2000000, params.BeaconConfig().MaxPer EpochActivationChurnLimit},

Check failure on line 383 in beacon-chain/core/helpers/validators_test.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: unexpected EpochActivationChurnLimit in composite literal; possibly missing comma or } (typecheck)

Check failure on line 383 in beacon-chain/core/helpers/validators_test.go

View workflow job for this annotation

GitHub Actions / Build

missing ',' in composite literal
}

defer ClearCache()

for _, test := range tests {
ClearCache()

// Create validators
validators := make([]*ethpb.Validator, test.validatorCount)
for i := range validators {
validators[i] = &ethpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
}

// Initialize beacon state
beaconState, err := state_native.InitializeFromProtoPhase0(&ethpb.BeaconState{
Slot: 1,
Validators: validators,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
})
require.NoError(t, err)
assert.Equal(t, test.wantedChurn, resultChurn, "ValidatorChurnLimit(%d)", test.validatorCount)

// Get active validator count
validatorCount, err := ActiveValidatorCount(context.Background(), beaconState, time.CurrentEpoch(beaconState))
require.NoError(t, err)

// Test churn limit calculation
resultChurn := ValidatorActivationChurnLimitDeneb(validatorCount)
assert.Equal(t, test.wantedChurn, resultChurn)
}
}

Expand Down
2 changes: 1 addition & 1 deletion beacon-chain/core/helpers/weak_subjectivity.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func ComputeWeakSubjectivityPeriod(ctx context.Context, st state.ReadOnlyBeaconS
T := cfg.MaxEffectiveBalance / cfg.GweiPerEth

// Validator churn limit.
delta, err := ValidatorChurnLimit(N)
delta := ValidatorExitChurnLimit(N)
if err != nil {
return 0, fmt.Errorf("cannot obtain active validator churn limit: %w", err)
}
Expand Down
15 changes: 3 additions & 12 deletions beacon-chain/core/validators/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ func InitiateValidatorExit(ctx context.Context, s state.BeaconState, idx primiti
if err != nil {
return nil, 0, errors.Wrap(err, "could not get active validator count")
}
currentChurn, err := helpers.ValidatorChurnLimit(activeValidatorCount)
if err != nil {
return nil, 0, errors.Wrap(err, "could not get churn limit")
}
currentChurn := helpers.ValidatorExitChurnLimit(activeValidatorCount)

if churn >= currentChurn {
exitQueueEpoch, err = exitQueueEpoch.SafeAdd(1)
Expand Down Expand Up @@ -235,10 +232,7 @@ func ExitedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validato
exitQueueChurn++
}
}
churn, err := helpers.ValidatorChurnLimit(activeValidatorCount)
if err != nil {
return nil, errors.Wrap(err, "could not get churn limit")
}
churn := helpers.ValidatorExitChurnLimit(activeValidatorCount)
if churn < exitQueueChurn {
exitQueueEpoch++
}
Expand Down Expand Up @@ -276,10 +270,7 @@ func EjectedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validat
exitQueueChurn++
}
}
churn, err := helpers.ValidatorChurnLimit(activeValidatorCount)
if err != nil {
return nil, errors.Wrap(err, "could not get churn limit")
}
churn := helpers.ValidatorExitChurnLimit(activeValidatorCount)
if churn < exitQueueChurn {
exitQueueEpoch++
}
Expand Down
11 changes: 6 additions & 5 deletions beacon-chain/rpc/prysm/v1alpha1/beacon/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,10 +601,6 @@ func (bs *Server) GetValidatorQueue(
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err)
}
churnLimit, err := helpers.ValidatorChurnLimit(activeValidatorCount)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not compute churn limit: %v", err)
}

exitQueueEpoch := primitives.Epoch(0)
for _, i := range exitEpochs {
Expand All @@ -619,7 +615,8 @@ func (bs *Server) GetValidatorQueue(
}
}
// Prevent churn limit from causing index out of bound issues.
if churnLimit < exitQueueChurn {
exitChurnLimit := helpers.ValidatorExitChurnLimit(activeValidatorCount)
if exitChurnLimit < exitQueueChurn {
// If we are above the churn limit, we simply increase the churn by one.
exitQueueEpoch++
}
Expand All @@ -645,6 +642,10 @@ func (bs *Server) GetValidatorQueue(
exitQueueKeys[i] = vals[idx].PublicKey
}

churnLimit := helpers.ValidatorActivationChurnLimit(activeValidatorCount)
if headState.Version() >= version.Deneb {
churnLimit = helpers.ValidatorActivationChurnLimitDeneb(activeValidatorCount)
}
return &ethpb.ValidatorQueue{
ChurnLimit: churnLimit,
ActivationPublicKeys: activationQueueKeys,
Expand Down
8 changes: 5 additions & 3 deletions beacon-chain/rpc/prysm/v1alpha1/beacon/validators_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/runtime/version"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
Expand Down Expand Up @@ -422,10 +423,11 @@ func (is *infostream) calculateActivationTimeForPendingValidators(res []*ethpb.V

// Loop over epochs, roughly simulating progression.
for curEpoch := epoch + 1; len(sortedIndices) > 0 && len(pendingValidators) > 0; curEpoch++ {
toProcess, err := helpers.ValidatorChurnLimit(numAttestingValidators)
if err != nil {
log.WithError(err).Error("Could not determine validator churn limit")
toProcess := helpers.ValidatorActivationChurnLimit(numAttestingValidators)
if headState.Version() >= version.Deneb {
toProcess = helpers.ValidatorActivationChurnLimitDeneb(numAttestingValidators)
}

if toProcess > uint64(len(sortedIndices)) {
toProcess = uint64(len(sortedIndices))
}
Expand Down
9 changes: 3 additions & 6 deletions beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1370,8 +1370,7 @@ func TestServer_GetValidatorQueue_PendingActivation(t *testing.T) {
}
activeValidatorCount, err := helpers.ActiveValidatorCount(context.Background(), headState, coreTime.CurrentEpoch(headState))
require.NoError(t, err)
wantChurn, err := helpers.ValidatorChurnLimit(activeValidatorCount)
require.NoError(t, err)
wantChurn := helpers.ValidatorActivationChurnLimit(activeValidatorCount)
assert.Equal(t, wantChurn, res.ChurnLimit)
assert.DeepEqual(t, wanted, res.ActivationPublicKeys)
wantedActiveIndices := []primitives.ValidatorIndex{2, 1, 0}
Expand Down Expand Up @@ -1412,8 +1411,7 @@ func TestServer_GetValidatorQueue_ExitedValidatorLeavesQueue(t *testing.T) {
}
activeValidatorCount, err := helpers.ActiveValidatorCount(context.Background(), headState, coreTime.CurrentEpoch(headState))
require.NoError(t, err)
wantChurn, err := helpers.ValidatorChurnLimit(activeValidatorCount)
require.NoError(t, err)
wantChurn := helpers.ValidatorExitChurnLimit(activeValidatorCount)
assert.Equal(t, wantChurn, res.ChurnLimit)
assert.DeepEqual(t, wanted, res.ExitPublicKeys)
wantedExitIndices := []primitives.ValidatorIndex{1}
Expand Down Expand Up @@ -1472,8 +1470,7 @@ func TestServer_GetValidatorQueue_PendingExit(t *testing.T) {
}
activeValidatorCount, err := helpers.ActiveValidatorCount(context.Background(), headState, coreTime.CurrentEpoch(headState))
require.NoError(t, err)
wantChurn, err := helpers.ValidatorChurnLimit(activeValidatorCount)
require.NoError(t, err)
wantChurn := helpers.ValidatorExitChurnLimit(activeValidatorCount)
assert.Equal(t, wantChurn, res.ChurnLimit)
assert.DeepEqual(t, wanted, res.ExitPublicKeys)
}
Expand Down

0 comments on commit fb91eb9

Please sign in to comment.