From 2542189efc81048f81191136e669fcbfb5c36d16 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Fri, 24 May 2024 19:12:38 -0500 Subject: [PATCH] eip-7251: process_effective_balance_updates (#14003) * eip-7251: process_effective_balance_updates Spectests for process_effective_balance_updates process_effective_balance_updates unit tests * PR feedback from the amazing @rkapka --------- Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com> --- beacon-chain/core/electra/BUILD.bazel | 1 + .../core/electra/effective_balance_updates.go | 42 ++++- .../electra/effective_balance_updates_test.go | 144 ++++++++++++++++++ .../electra/epoch_processing/BUILD.bazel | 1 + .../effective_balance_updates_test.go | 11 ++ .../electra/epoch_processing/BUILD.bazel | 1 + .../effective_balance_updates_test.go | 11 ++ .../electra/epoch_processing/BUILD.bazel | 1 + .../effective_balance_updates.go | 30 ++++ 9 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 beacon-chain/core/electra/effective_balance_updates_test.go create mode 100644 testing/spectest/mainnet/electra/epoch_processing/effective_balance_updates_test.go create mode 100644 testing/spectest/minimal/electra/epoch_processing/effective_balance_updates_test.go create mode 100644 testing/spectest/shared/electra/epoch_processing/effective_balance_updates.go diff --git a/beacon-chain/core/electra/BUILD.bazel b/beacon-chain/core/electra/BUILD.bazel index ded97b035efd..42dfffdb0e74 100644 --- a/beacon-chain/core/electra/BUILD.bazel +++ b/beacon-chain/core/electra/BUILD.bazel @@ -43,6 +43,7 @@ go_test( "churn_test.go", "consolidations_test.go", "deposits_test.go", + "effective_balance_updates_test.go", "upgrade_test.go", "validator_test.go", ], diff --git a/beacon-chain/core/electra/effective_balance_updates.go b/beacon-chain/core/electra/effective_balance_updates.go index 256fad220554..0458d3550e88 100644 --- a/beacon-chain/core/electra/effective_balance_updates.go +++ b/beacon-chain/core/electra/effective_balance_updates.go @@ -1,6 +1,13 @@ package electra -import "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" +import ( + "fmt" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/config/params" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" +) // ProcessEffectiveBalanceUpdates processes effective balance updates during epoch processing. // @@ -24,6 +31,35 @@ import "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" // ): // validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, EFFECTIVE_BALANCE_LIMIT) func ProcessEffectiveBalanceUpdates(state state.BeaconState) error { - // TODO: replace with real implementation - return nil + effBalanceInc := params.BeaconConfig().EffectiveBalanceIncrement + hysteresisInc := effBalanceInc / params.BeaconConfig().HysteresisQuotient + downwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisDownwardMultiplier + upwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisUpwardMultiplier + + bals := state.Balances() + + // Update effective balances with hysteresis. + validatorFunc := func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) { + if val == nil { + return false, nil, fmt.Errorf("validator %d is nil in state", idx) + } + if idx >= len(bals) { + return false, nil, fmt.Errorf("validator index exceeds validator length in state %d >= %d", idx, len(state.Balances())) + } + balance := bals[idx] + + effectiveBalanceLimit := params.BeaconConfig().MinActivationBalance + if helpers.HasCompoundingWithdrawalCredential(val) { + effectiveBalanceLimit = params.BeaconConfig().MaxEffectiveBalanceElectra + } + + if balance+downwardThreshold < val.EffectiveBalance || val.EffectiveBalance+upwardThreshold < balance { + effectiveBal := min(balance-balance%effBalanceInc, effectiveBalanceLimit) + val.EffectiveBalance = effectiveBal + return false, val, nil + } + return false, val, nil + } + + return state.ApplyToEveryValidator(validatorFunc) } diff --git a/beacon-chain/core/electra/effective_balance_updates_test.go b/beacon-chain/core/electra/effective_balance_updates_test.go new file mode 100644 index 000000000000..8d3350fb4d7d --- /dev/null +++ b/beacon-chain/core/electra/effective_balance_updates_test.go @@ -0,0 +1,144 @@ +package electra_test + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/config/params" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func TestProcessEffectiveBalnceUpdates(t *testing.T) { + effBalanceInc := params.BeaconConfig().EffectiveBalanceIncrement + hysteresisInc := effBalanceInc / params.BeaconConfig().HysteresisQuotient + downwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisDownwardMultiplier + upwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisUpwardMultiplier + + tests := []struct { + name string + state state.BeaconState + wantErr bool + check func(*testing.T, state.BeaconState) + }{ + { + name: "validator with compounding withdrawal credentials updates effective balance", + state: func() state.BeaconState { + pb := ð.BeaconStateElectra{ + Validators: []*eth.Validator{ + { + EffectiveBalance: params.BeaconConfig().MinActivationBalance, + WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte, 0x11}, + }, + }, + Balances: []uint64{ + params.BeaconConfig().MaxEffectiveBalanceElectra * 2, + }, + } + st, err := state_native.InitializeFromProtoElectra(pb) + require.NoError(t, err) + return st + }(), + check: func(t *testing.T, bs state.BeaconState) { + val, err := bs.ValidatorAtIndex(0) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxEffectiveBalanceElectra, val.EffectiveBalance) + }, + }, + { + name: "validator without compounding withdrawal credentials updates effective balance", + state: func() state.BeaconState { + pb := ð.BeaconStateElectra{ + Validators: []*eth.Validator{ + { + EffectiveBalance: params.BeaconConfig().MinActivationBalance / 2, + WithdrawalCredentials: nil, + }, + }, + Balances: []uint64{ + params.BeaconConfig().MaxEffectiveBalanceElectra, + }, + } + st, err := state_native.InitializeFromProtoElectra(pb) + require.NoError(t, err) + return st + }(), + check: func(t *testing.T, bs state.BeaconState) { + val, err := bs.ValidatorAtIndex(0) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, val.EffectiveBalance) + }, + }, + { + name: "validator effective balance moves only when outside of threshold", + state: func() state.BeaconState { + pb := ð.BeaconStateElectra{ + Validators: []*eth.Validator{ + { + EffectiveBalance: params.BeaconConfig().MinActivationBalance, + WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte, 0x11}, + }, + { + EffectiveBalance: params.BeaconConfig().MinActivationBalance, + WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte, 0x11}, + }, + { + EffectiveBalance: params.BeaconConfig().MinActivationBalance, + WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte, 0x11}, + }, + { + EffectiveBalance: params.BeaconConfig().MinActivationBalance, + WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte, 0x11}, + }, + }, + Balances: []uint64{ + params.BeaconConfig().MinActivationBalance - downwardThreshold - 1, // beyond downward threshold + params.BeaconConfig().MinActivationBalance - downwardThreshold + 1, // within downward threshold + params.BeaconConfig().MinActivationBalance + upwardThreshold + 1, // beyond upward threshold + params.BeaconConfig().MinActivationBalance + upwardThreshold - 1, // within upward threshold + }, + } + st, err := state_native.InitializeFromProtoElectra(pb) + require.NoError(t, err) + return st + }(), + check: func(t *testing.T, bs state.BeaconState) { + // validator 0 has a balance diff exceeding the threshold so a diff should be applied to + // effective balance and it moves by effective balance increment. + val, err := bs.ValidatorAtIndex(0) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance-params.BeaconConfig().EffectiveBalanceIncrement, val.EffectiveBalance) + + // validator 1 has a balance diff within the threshold so the effective balance should not + // have changed. + val, err = bs.ValidatorAtIndex(1) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, val.EffectiveBalance) + + // Validator 2 has a balance diff exceeding the threshold so a diff should be applied to the + // effective balance and it moves by effective balance increment. + val, err = bs.ValidatorAtIndex(2) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance+params.BeaconConfig().EffectiveBalanceIncrement, val.EffectiveBalance) + + // Validator 3 has a balance diff within the threshold so the effective balance should not + // have changed. + val, err = bs.ValidatorAtIndex(3) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, val.EffectiveBalance) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := electra.ProcessEffectiveBalanceUpdates(tt.state) + require.Equal(t, tt.wantErr, err != nil, "unexpected error returned wanted error=nil (%s), got error=%s", tt.wantErr, err) + if tt.check != nil { + tt.check(t, tt.state) + } + }) + } +} diff --git a/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel b/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel index 7a22e82c49fb..cd8b0de62edf 100644 --- a/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/mainnet/electra/epoch_processing/BUILD.bazel @@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_test") go_test( name = "go_default_test", srcs = [ + "effective_balance_updates_test.go", "eth1_data_reset_test.go", "historical_summaries_update_test.go", "inactivity_updates_test.go", diff --git a/testing/spectest/mainnet/electra/epoch_processing/effective_balance_updates_test.go b/testing/spectest/mainnet/electra/epoch_processing/effective_balance_updates_test.go new file mode 100644 index 000000000000..22532aed6f0d --- /dev/null +++ b/testing/spectest/mainnet/electra/epoch_processing/effective_balance_updates_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_EffectiveBalanceUpdates(t *testing.T) { + epoch_processing.RunEffectiveBalanceUpdatesTests(t, "mainnet") +} diff --git a/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel b/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel index f9d84a32e914..162e4f072fe7 100644 --- a/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/minimal/electra/epoch_processing/BUILD.bazel @@ -3,6 +3,7 @@ load("@prysm//tools/go:def.bzl", "go_test") go_test( name = "go_default_test", srcs = [ + "effective_balance_updates_test.go", "eth1_data_reset_test.go", "historical_summaries_update_test.go", "inactivity_updates_test.go", diff --git a/testing/spectest/minimal/electra/epoch_processing/effective_balance_updates_test.go b/testing/spectest/minimal/electra/epoch_processing/effective_balance_updates_test.go new file mode 100644 index 000000000000..0f14281f917e --- /dev/null +++ b/testing/spectest/minimal/electra/epoch_processing/effective_balance_updates_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_EffectiveBalanceUpdates(t *testing.T) { + epoch_processing.RunEffectiveBalanceUpdatesTests(t, "minimal") +} diff --git a/testing/spectest/shared/electra/epoch_processing/BUILD.bazel b/testing/spectest/shared/electra/epoch_processing/BUILD.bazel index 0f37ce9b7437..f651ce5c648e 100644 --- a/testing/spectest/shared/electra/epoch_processing/BUILD.bazel +++ b/testing/spectest/shared/electra/epoch_processing/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", testonly = True, srcs = [ + "effective_balance_updates.go", "eth1_data_reset.go", "helpers.go", "historical_summaries_update.go", diff --git a/testing/spectest/shared/electra/epoch_processing/effective_balance_updates.go b/testing/spectest/shared/electra/epoch_processing/effective_balance_updates.go new file mode 100644 index 000000000000..7396ef0cd629 --- /dev/null +++ b/testing/spectest/shared/electra/epoch_processing/effective_balance_updates.go @@ -0,0 +1,30 @@ +package epoch_processing + +import ( + "path" + "testing" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/spectest/utils" +) + +// RunEffectiveBalanceUpdatesTests executes "epoch_processing/effective_balance_updates" tests. +func RunEffectiveBalanceUpdatesTests(t *testing.T, config string) { + require.NoError(t, utils.SetConfig(t, config)) + + testFolders, testsFolderPath := utils.TestFolders(t, config, "electra", "epoch_processing/effective_balance_updates/pyspec_tests") + for _, folder := range testFolders { + t.Run(folder.Name(), func(t *testing.T) { + folderPath := path.Join(testsFolderPath, folder.Name()) + RunEpochOperationTest(t, folderPath, processEffectiveBalanceUpdatesWrapper) + }) + } +} + +func processEffectiveBalanceUpdatesWrapper(t *testing.T, st state.BeaconState) (state.BeaconState, error) { + err := electra.ProcessEffectiveBalanceUpdates(st) + require.NoError(t, err, "Could not process final updates") + return st, nil +}