mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 13:28:01 -05:00
Compare commits
8 Commits
ba2333069a
...
eip-7251-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48d7e9c292 | ||
|
|
8a1b0e2c4c | ||
|
|
7685a0d2f7 | ||
|
|
8fa05b7f69 | ||
|
|
8a6b73c0fd | ||
|
|
c5d4193bb4 | ||
|
|
49290fd2d6 | ||
|
|
414b8ace75 |
@@ -48,6 +48,7 @@ go_test(
|
||||
"consolidations_test.go",
|
||||
"deposits_test.go",
|
||||
"effective_balance_updates_test.go",
|
||||
"registry_updates_test.go",
|
||||
"upgrade_test.go",
|
||||
"validator_test.go",
|
||||
"withdrawals_test.go",
|
||||
|
||||
@@ -2,12 +2,18 @@ package electra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
)
|
||||
|
||||
// ProcessRegistryUpdates rotates validators in and out of active pool.
|
||||
// the amount to rotate is determined churn limit.
|
||||
// ProcessRegistryUpdates processes all validators eligible for the activation queue, all validators
|
||||
// which should be ejected, and all validators which are eligible for activation from the queue.
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
@@ -28,7 +34,63 @@ import (
|
||||
// for validator in state.validators:
|
||||
// if is_eligible_for_activation(state, validator):
|
||||
// validator.activation_epoch = activation_epoch
|
||||
func ProcessRegistryUpdates(ctx context.Context, state state.BeaconState) (state.BeaconState, error) {
|
||||
// TODO: replace with real implementation
|
||||
return state, nil
|
||||
func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) error {
|
||||
currentEpoch := time.CurrentEpoch(st)
|
||||
ejectionBal := params.BeaconConfig().EjectionBalance
|
||||
activationEpoch := helpers.ActivationExitEpoch(currentEpoch)
|
||||
|
||||
finalizedEpoch := st.FinalizedCheckpointEpoch()
|
||||
eligibleForActivationQueueValidators := make([]primitives.ValidatorIndex, 0)
|
||||
eligibleForActivationValidators := make([]primitives.ValidatorIndex, 0)
|
||||
if err := st.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error {
|
||||
alreadyActivated := false
|
||||
if helpers.IsEligibleForActivationQueue(val, currentEpoch) {
|
||||
eligibleForActivationQueueValidators = append(eligibleForActivationQueueValidators, primitives.ValidatorIndex(idx))
|
||||
|
||||
if currentEpoch+1 <= finalizedEpoch && val.ActivationEpoch() == params.BeaconConfig().FarFutureEpoch {
|
||||
eligibleForActivationValidators = append(eligibleForActivationValidators, primitives.ValidatorIndex(idx))
|
||||
alreadyActivated = true
|
||||
}
|
||||
}
|
||||
|
||||
if val.EffectiveBalance() <= ejectionBal && helpers.IsActiveValidator(val, currentEpoch) {
|
||||
var err error
|
||||
// exitQueueEpoch and churn arguments are not used in electra.
|
||||
st, _, err = validators.InitiateValidatorExit(ctx, st, primitives.ValidatorIndex(idx), 0 /*exitQueueEpoch*/, 0 /*churn*/)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initiate validator exit at index %d: %w", idx, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyActivated && helpers.IsEligibleForActivation(st, val) {
|
||||
eligibleForActivationValidators = append(eligibleForActivationValidators, primitives.ValidatorIndex(idx))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, idx := range eligibleForActivationQueueValidators {
|
||||
val, err := st.ValidatorAtIndex(idx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get validator at index %d: %w", idx, err)
|
||||
}
|
||||
val.ActivationEligibilityEpoch = currentEpoch + 1
|
||||
if err := st.UpdateValidatorAtIndex(idx, val); err != nil {
|
||||
return fmt.Errorf("failed to update eligible validator at index %d: %w", idx, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, idx := range eligibleForActivationValidators {
|
||||
val, err := st.ValidatorAtIndex(idx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get validator at index %d: %w", idx, err)
|
||||
}
|
||||
val.ActivationEpoch = activationEpoch
|
||||
if err := st.UpdateValidatorAtIndex(idx, val); err != nil {
|
||||
return fmt.Errorf("failed to activate validator at index %d: %w", idx, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
148
beacon-chain/core/electra/registry_updates_test.go
Normal file
148
beacon-chain/core/electra/registry_updates_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package electra_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
)
|
||||
|
||||
func TestProcessRegistryUpdates(t *testing.T) {
|
||||
const electraEpoch = 3
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.ElectraForkEpoch = electraEpoch
|
||||
params.SetActiveTestCleanup(t, cfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
state state.BeaconState
|
||||
check func(*testing.T, state.BeaconState)
|
||||
}{
|
||||
{
|
||||
name: "No rotation",
|
||||
state: func() state.BeaconState {
|
||||
base := ð.BeaconStateElectra{
|
||||
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
|
||||
Validators: []*eth.Validator{
|
||||
{ExitEpoch: params.BeaconConfig().MaxSeedLookahead},
|
||||
{ExitEpoch: params.BeaconConfig().MaxSeedLookahead},
|
||||
},
|
||||
Balances: []uint64{
|
||||
params.BeaconConfig().MaxEffectiveBalance,
|
||||
params.BeaconConfig().MaxEffectiveBalance,
|
||||
},
|
||||
FinalizedCheckpoint: ð.Checkpoint{Root: make([]byte, fieldparams.RootLength)},
|
||||
}
|
||||
st, err := state_native.InitializeFromProtoElectra(base)
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
for i, val := range st.Validators() {
|
||||
require.Equal(t, params.BeaconConfig().MaxSeedLookahead, val.ExitEpoch, "validator updated unexpectedly at index %d", i)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Validators are activated",
|
||||
state: func() state.BeaconState {
|
||||
base := ð.BeaconStateElectra{
|
||||
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
|
||||
FinalizedCheckpoint: ð.Checkpoint{Epoch: 6, Root: make([]byte, fieldparams.RootLength)},
|
||||
}
|
||||
for i := uint64(0); i < 10; i++ {
|
||||
base.Validators = append(base.Validators, ð.Validator{
|
||||
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
})
|
||||
}
|
||||
st, err := state_native.InitializeFromProtoElectra(base)
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
activationEpoch := helpers.ActivationExitEpoch(5)
|
||||
// All validators should be activated.
|
||||
for i, val := range st.Validators() {
|
||||
require.Equal(t, activationEpoch, val.ActivationEpoch, "failed to update validator at index %d", i)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Validators are exited",
|
||||
state: func() state.BeaconState {
|
||||
base := ð.BeaconStateElectra{
|
||||
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
|
||||
FinalizedCheckpoint: ð.Checkpoint{Epoch: 6, Root: make([]byte, fieldparams.RootLength)},
|
||||
}
|
||||
for i := uint64(0); i < 10; i++ {
|
||||
base.Validators = append(base.Validators, ð.Validator{
|
||||
EffectiveBalance: params.BeaconConfig().EjectionBalance - 1,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
})
|
||||
}
|
||||
st, err := state_native.InitializeFromProtoElectra(base)
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
// All validators should be exited
|
||||
for i, val := range st.Validators() {
|
||||
require.NotEqual(t, params.BeaconConfig().FarFutureEpoch, val.ExitEpoch, "failed to update exit epoch on validator %d", i)
|
||||
require.NotEqual(t, params.BeaconConfig().FarFutureEpoch, val.WithdrawableEpoch, "failed to update withdrawable epoch on validator %d", i)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := electra.ProcessRegistryUpdates(context.TODO(), tt.state)
|
||||
require.NoError(t, err)
|
||||
if tt.check != nil {
|
||||
tt.check(t, tt.state)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ProcessRegistryUpdates_MassEjection(b *testing.B) {
|
||||
bal := params.BeaconConfig().EjectionBalance - 1
|
||||
ffe := params.BeaconConfig().FarFutureEpoch
|
||||
genValidators := func(num uint64) []*eth.Validator {
|
||||
vals := make([]*eth.Validator, num)
|
||||
for i := range vals {
|
||||
vals[i] = ð.Validator{
|
||||
EffectiveBalance: bal,
|
||||
ExitEpoch: ffe,
|
||||
}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
st, err := util.NewBeaconStateElectra()
|
||||
require.NoError(b, err)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
if err := st.SetValidators(genValidators(100000)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
|
||||
if err := electra.ProcessRegistryUpdates(context.TODO(), st); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -74,8 +74,7 @@ func ProcessEpoch(ctx context.Context, state state.BeaconState) error {
|
||||
return errors.Wrap(err, "could not process rewards and penalties")
|
||||
}
|
||||
|
||||
state, err = ProcessRegistryUpdates(ctx, state)
|
||||
if err != nil {
|
||||
if err := ProcessRegistryUpdates(ctx, state); err != nil {
|
||||
return errors.Wrap(err, "could not process registry updates")
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ var (
|
||||
// Check if ``validator`` is active.
|
||||
// """
|
||||
// return validator.activation_epoch <= epoch < validator.exit_epoch
|
||||
func IsActiveValidator(validator *ethpb.Validator, epoch primitives.Epoch) bool {
|
||||
return checkValidatorActiveStatus(validator.ActivationEpoch, validator.ExitEpoch, epoch)
|
||||
func IsActiveValidator(validator state.ReadOnlyValidator, epoch primitives.Epoch) bool {
|
||||
return checkValidatorActiveStatus(validator.ActivationEpoch(), validator.ExitEpoch(), epoch)
|
||||
}
|
||||
|
||||
// IsActiveValidatorUsingTrie checks if a read only validator is active.
|
||||
@@ -404,11 +404,11 @@ func ComputeProposerIndex(bState state.ReadOnlyValidators, activeIndices []primi
|
||||
// validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH
|
||||
// and validator.effective_balance >= MIN_ACTIVATION_BALANCE # [Modified in Electra:EIP7251]
|
||||
// )
|
||||
func IsEligibleForActivationQueue(validator *ethpb.Validator, currentEpoch primitives.Epoch) bool {
|
||||
func IsEligibleForActivationQueue(validator state.ReadOnlyValidator, currentEpoch primitives.Epoch) bool {
|
||||
if currentEpoch >= params.BeaconConfig().ElectraForkEpoch {
|
||||
return isEligibleForActivationQueueElectra(validator.ActivationEligibilityEpoch, validator.EffectiveBalance)
|
||||
return isEligibleForActivationQueueElectra(validator.ActivationEligibilityEpoch(), validator.EffectiveBalance())
|
||||
}
|
||||
return isEligibleForActivationQueue(validator.ActivationEligibilityEpoch, validator.EffectiveBalance)
|
||||
return isEligibleForActivationQueue(validator.ActivationEligibilityEpoch(), validator.EffectiveBalance())
|
||||
}
|
||||
|
||||
// isEligibleForActivationQueue carries out the logic for IsEligibleForActivationQueue
|
||||
@@ -459,9 +459,9 @@ func isEligibleForActivationQueueElectra(activationEligibilityEpoch primitives.E
|
||||
// # Has not yet been activated
|
||||
// and validator.activation_epoch == FAR_FUTURE_EPOCH
|
||||
// )
|
||||
func IsEligibleForActivation(state state.ReadOnlyCheckpoint, validator *ethpb.Validator) bool {
|
||||
func IsEligibleForActivation(state state.ReadOnlyCheckpoint, validator state.ReadOnlyValidator) bool {
|
||||
finalizedEpoch := state.FinalizedCheckpointEpoch()
|
||||
return isEligibleForActivation(validator.ActivationEligibilityEpoch, validator.ActivationEpoch, finalizedEpoch)
|
||||
return isEligibleForActivation(validator.ActivationEligibilityEpoch(), validator.ActivationEpoch(), finalizedEpoch)
|
||||
}
|
||||
|
||||
// IsEligibleForActivationUsingTrie checks if the validator is eligible for activation.
|
||||
|
||||
@@ -12,6 +12,7 @@ go_test(
|
||||
"pending_balance_updates_test.go",
|
||||
"pending_consolidations_test.go",
|
||||
"randao_mixes_reset_test.go",
|
||||
"registry_updates_test.go",
|
||||
"rewards_and_penalties_test.go",
|
||||
"slashings_reset_test.go",
|
||||
"slashings_test.go",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package epoch_processing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/epoch_processing"
|
||||
)
|
||||
|
||||
func TestMainnet_Electra_EpochProcessing_RegistryUpdates(t *testing.T) {
|
||||
epoch_processing.RunRegistryUpdatesTests(t, "mainnet")
|
||||
}
|
||||
@@ -12,6 +12,7 @@ go_test(
|
||||
"pending_balance_updates_test.go",
|
||||
"pending_consolidations_test.go",
|
||||
"randao_mixes_reset_test.go",
|
||||
"registry_updates_test.go",
|
||||
"rewards_and_penalties_test.go",
|
||||
"slashings_reset_test.go",
|
||||
"slashings_test.go",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package epoch_processing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/epoch_processing"
|
||||
)
|
||||
|
||||
func TestMinimal_Electra_EpochProcessing_RegistryUpdates(t *testing.T) {
|
||||
epoch_processing.RunRegistryUpdatesTests(t, "minimal")
|
||||
}
|
||||
@@ -14,6 +14,7 @@ go_library(
|
||||
"pending_balance_updates.go",
|
||||
"pending_consolidations.go",
|
||||
"randao_mixes_reset.go",
|
||||
"registry_updates.go",
|
||||
"rewards_and_penalties.go",
|
||||
"slashings.go",
|
||||
"slashings_reset.go",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package epoch_processing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/spectest/utils"
|
||||
)
|
||||
|
||||
// RunRegistryUpdatesTests executes "epoch_processing/registry_updates" tests.
|
||||
func RunRegistryUpdatesTests(t *testing.T, config string) {
|
||||
require.NoError(t, utils.SetConfig(t, config))
|
||||
|
||||
testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "epoch_processing/registry_updates/pyspec_tests")
|
||||
for _, folder := range testFolders {
|
||||
t.Run(folder.Name(), func(t *testing.T) {
|
||||
// Important to clear cache for every test or else the old value of active validator count gets reused.
|
||||
helpers.ClearCache()
|
||||
folderPath := path.Join(testsFolderPath, folder.Name())
|
||||
RunEpochOperationTest(t, folderPath, processRegistryUpdatesWrapper)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func processRegistryUpdatesWrapper(_ *testing.T, state state.BeaconState) (state.BeaconState, error) {
|
||||
return state, electra.ProcessRegistryUpdates(context.Background(), state)
|
||||
}
|
||||
Reference in New Issue
Block a user