From 7409de1a66db4f5c1416e5a62701d742bb75e08f Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 15:34:31 -0500 Subject: [PATCH 01/16] adding handling for ActivationEligibilityEpoch --- consensus-types/types/validator.go | 24 ++++++------ state-transition/core/state_processor.go | 3 ++ .../core/state_processor_genesis.go | 29 +++++++++++++- .../core/state_processor_genesis_test.go | 30 +++++++------- .../core/state_processor_validators.go | 39 +++++++++++++++++++ state-transition/core/types.go | 5 +++ 6 files changed, 104 insertions(+), 26 deletions(-) diff --git a/consensus-types/types/validator.go b/consensus-types/types/validator.go index 89996ae6f..79630c6e1 100644 --- a/consensus-types/types/validator.go +++ b/consensus-types/types/validator.go @@ -253,17 +253,13 @@ func (v Validator) IsEligibleForActivation( v.ActivationEpoch == math.Epoch(constants.FarFutureEpoch) } -// IsEligibleForActivationQueue as defined in the Ethereum 2.0 Spec +// IsEligibleForActivationQueue is defined slightly differently from Ethereum 2.0 Spec // https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#is_eligible_for_activation_queue // //nolint:lll -func (v Validator) IsEligibleForActivationQueue( - maxEffectiveBalance math.Gwei, -) bool { - return v.ActivationEligibilityEpoch == math.Epoch( - constants.FarFutureEpoch, - ) && - v.EffectiveBalance == maxEffectiveBalance +func (v Validator) IsEligibleForActivationQueue(threshold math.Gwei) bool { + return v.ActivationEligibilityEpoch == math.Epoch(constants.FarFutureEpoch) && + v.EffectiveBalance >= threshold } // IsSlashable as defined in the Ethereum 2.0 Spec @@ -296,9 +292,7 @@ func (v Validator) IsFullyWithdrawable( // https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#is_partially_withdrawable_validator // //nolint:lll -func (v Validator) IsPartiallyWithdrawable( - balance, maxEffectiveBalance math.Gwei, -) bool { +func (v Validator) IsPartiallyWithdrawable(balance, maxEffectiveBalance math.Gwei) bool { hasExcessBalance := balance > maxEffectiveBalance return v.HasEth1WithdrawalCredentials() && v.HasMaxEffectiveBalance(maxEffectiveBalance) && hasExcessBalance @@ -335,6 +329,14 @@ func (v Validator) GetWithdrawableEpoch() math.Epoch { return v.WithdrawableEpoch } +func (v *Validator) SetActivationEligibilityEpoch(e math.Epoch) { + v.ActivationEligibilityEpoch = e +} + +func (v *Validator) GetActivationEligibilityEpoch() math.Epoch { + return v.ActivationEligibilityEpoch +} + // GetWithdrawalCredentials returns the withdrawal credentials of the validator. func (v Validator) GetWithdrawalCredentials() WithdrawalCredentials { return v.WithdrawalCredentials diff --git a/state-transition/core/state_processor.go b/state-transition/core/state_processor.go index 422daf6cf..f67c998bb 100644 --- a/state-transition/core/state_processor.go +++ b/state-transition/core/state_processor.go @@ -398,6 +398,9 @@ func (sp *StateProcessor[ // no real need to perform hollowProcessRewardsAndPenalties } + if err = sp.processRegistryUpdates(st); err != nil { + return nil, err + } if err = sp.processEffectiveBalanceUpdates(st); err != nil { return nil, err } diff --git a/state-transition/core/state_processor_genesis.go b/state-transition/core/state_processor_genesis.go index 1335abdd6..d65685d83 100644 --- a/state-transition/core/state_processor_genesis.go +++ b/state-transition/core/state_processor_genesis.go @@ -21,6 +21,8 @@ package core import ( + "fmt" + "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/constants" @@ -32,7 +34,7 @@ import ( // InitializePreminedBeaconStateFromEth1 initializes the beacon state. // -//nolint:gocognit // todo fix. +//nolint:gocognit,funlen,lll // todo fix. func (sp *StateProcessor[ _, BeaconBlockBodyT, BeaconBlockHeaderT, BeaconStateT, _, DepositT, Eth1DataT, _, ExecutionPayloadHeaderT, ForkT, _, _, ValidatorT, _, _, _, _, @@ -103,6 +105,31 @@ func (sp *StateProcessor[ } } + // process activations + switch { + case sp.cs.DepositEth1ChainID() == spec.BartioChainID: + // nothing to do + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID: + // nothing to do + default: + vals, err := st.GetValidators() + if err != nil { + return nil, fmt.Errorf("genesis activation, failed listing validators: %w", err) + } + + var idx math.ValidatorIndex + for _, val := range vals { + val.SetActivationEligibilityEpoch(0) + idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) + if err != nil { + return nil, err + } + if err = st.UpdateValidatorAtIndex(idx, val); err != nil { + return nil, err + } + } + } + // Handle special case bartio genesis. validatorsRoot := common.Root(hex.MustToBytes(spec.BartioValRoot)) if sp.cs.DepositEth1ChainID() != spec.BartioChainID { diff --git a/state-transition/core/state_processor_genesis_test.go b/state-transition/core/state_processor_genesis_test.go index 2f40614f8..730e5e65f 100644 --- a/state-transition/core/state_processor_genesis_test.go +++ b/state-transition/core/state_processor_genesis_test.go @@ -145,12 +145,18 @@ func checkValidatorNonBartio( ) { t.Helper() + idx, err := bs.ValidatorIndexByPubkey(dep.Pubkey) + require.NoError(t, err) + + val, err := bs.ValidatorByIndex(idx) + require.NoError(t, err) + require.Equal(t, dep.Pubkey, val.Pubkey) + // checks on validators common to all networks - commonChecksValidators(t, cs, bs, dep) + commonChecksValidators(t, cs, val, dep) // checks on validators for any network but Bartio - idx, err := bs.ValidatorIndexByPubkey(dep.Pubkey) - require.NoError(t, err) + require.Equal(t, math.Epoch(0), val.GetActivationEligibilityEpoch()) valBal, err := bs.GetBalance(idx) require.NoError(t, err) @@ -268,15 +274,17 @@ func checkValidatorBartio( ) { t.Helper() - // checks on validators common to all networks - commonChecksValidators(t, cs, bs, dep) - - // Bartio specific checks on validators idx, err := bs.ValidatorIndexByPubkey(dep.Pubkey) require.NoError(t, err) + val, err := bs.ValidatorByIndex(idx) require.NoError(t, err) + require.Equal(t, dep.Pubkey, val.Pubkey) + + // checks on validators common to all networks + commonChecksValidators(t, cs, val, dep) + // Bartio specific checks on validators valBal, err := bs.GetBalance(idx) require.NoError(t, err) require.Equal(t, val.EffectiveBalance, valBal) @@ -291,16 +299,10 @@ func commonChecksValidators( math.Slot, any, ], - bs *TestBeaconStateT, + val *types.Validator, dep *types.Deposit, ) { t.Helper() - - idx, err := bs.ValidatorIndexByPubkey(dep.Pubkey) - require.NoError(t, err) - - val, err := bs.ValidatorByIndex(idx) - require.NoError(t, err) require.Equal(t, dep.Pubkey, val.Pubkey) var ( diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index 869153cfe..953646ab7 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -21,12 +21,51 @@ package core import ( + "fmt" + "github.com/berachain/beacon-kit/primitives/bytes" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/transition" "github.com/sourcegraph/conc/iter" ) +//nolint:lll +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, +]) processRegistryUpdates( + st BeaconStateT, +) error { + vals, err := st.GetValidators() + if err != nil { + return fmt.Errorf("registry update, failed listing validators: %w", err) + } + + slot, err := st.GetSlot() + if err != nil { + return fmt.Errorf("registry update, failed loading slot: %w", err) + } + currEpoch := sp.cs.SlotToEpoch(slot) + nextEpoch := currEpoch + 1 + + minEffectiveBalance := math.Gwei(sp.cs.EjectionBalance() + sp.cs.EffectiveBalanceIncrement()) + + var idx math.ValidatorIndex + for si, val := range vals { + if val.IsEligibleForActivationQueue(minEffectiveBalance) { + val.SetActivationEligibilityEpoch(nextEpoch) + idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) + if err != nil { + return fmt.Errorf("registry update, failed loading validator index, state index %d: %w", si, err) + } + if err = st.UpdateValidatorAtIndex(idx, val); err != nil { + return fmt.Errorf("registry update, failed updating validator idx %d: %w", idx, err) + } + } + } + + return nil +} + // processValidatorsSetUpdates returns the validators set updates that // will be used by consensus. func (sp *StateProcessor[ diff --git a/state-transition/core/types.go b/state-transition/core/types.go index 49b9f952c..9412acb90 100644 --- a/state-transition/core/types.go +++ b/state-transition/core/types.go @@ -242,6 +242,8 @@ type Validator[ ) ValidatorT // IsSlashed returns true if the validator is slashed. IsSlashed() bool + + IsEligibleForActivationQueue(threshold math.Gwei) bool // GetPubkey returns the public key of the validator. GetPubkey() crypto.BLSPubkey // GetEffectiveBalance returns the effective balance of the validator in @@ -253,6 +255,9 @@ type Validator[ GetWithdrawableEpoch() math.Epoch // SetWithdrawableEpoch sets the epoch when the validator can withdraw. SetWithdrawableEpoch(math.Epoch) + + GetActivationEligibilityEpoch() math.Epoch + SetActivationEligibilityEpoch(math.Epoch) } type Validators interface { From 51a12a06c00a927c7999fff0736b1d0b1c6a4579 Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 16:37:10 -0500 Subject: [PATCH 02/16] adding handling for ActivationEpoch --- consensus-types/types/validator.go | 12 +++++++++--- state-transition/core/state_processor_genesis.go | 1 + .../core/state_processor_genesis_test.go | 12 ++++++++++++ state-transition/core/state_processor_validators.go | 10 ++++++++++ state-transition/core/types.go | 5 +++++ 5 files changed, 37 insertions(+), 3 deletions(-) diff --git a/consensus-types/types/validator.go b/consensus-types/types/validator.go index 79630c6e1..b7dcb8e87 100644 --- a/consensus-types/types/validator.go +++ b/consensus-types/types/validator.go @@ -246,9 +246,7 @@ func (v Validator) IsActive(epoch math.Epoch) bool { // https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#is_eligible_for_activation_queue // //nolint:lll -func (v Validator) IsEligibleForActivation( - finalizedEpoch math.Epoch, -) bool { +func (v Validator) IsEligibleForActivation(finalizedEpoch math.Epoch) bool { return v.ActivationEligibilityEpoch <= finalizedEpoch && v.ActivationEpoch == math.Epoch(constants.FarFutureEpoch) } @@ -337,6 +335,14 @@ func (v *Validator) GetActivationEligibilityEpoch() math.Epoch { return v.ActivationEligibilityEpoch } +func (v *Validator) SetActivationEpoch(e math.Epoch) { + v.ActivationEpoch = e +} + +func (v *Validator) GetActivationEpoch() math.Epoch { + return v.ActivationEpoch +} + // GetWithdrawalCredentials returns the withdrawal credentials of the validator. func (v Validator) GetWithdrawalCredentials() WithdrawalCredentials { return v.WithdrawalCredentials diff --git a/state-transition/core/state_processor_genesis.go b/state-transition/core/state_processor_genesis.go index d65685d83..bce1f1fc7 100644 --- a/state-transition/core/state_processor_genesis.go +++ b/state-transition/core/state_processor_genesis.go @@ -120,6 +120,7 @@ func (sp *StateProcessor[ var idx math.ValidatorIndex for _, val := range vals { val.SetActivationEligibilityEpoch(0) + val.SetActivationEpoch(0) idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) if err != nil { return nil, err diff --git a/state-transition/core/state_processor_genesis_test.go b/state-transition/core/state_processor_genesis_test.go index 730e5e65f..8cdf496f4 100644 --- a/state-transition/core/state_processor_genesis_test.go +++ b/state-transition/core/state_processor_genesis_test.go @@ -157,6 +157,7 @@ func checkValidatorNonBartio( // checks on validators for any network but Bartio require.Equal(t, math.Epoch(0), val.GetActivationEligibilityEpoch()) + require.Equal(t, math.Epoch(0), val.GetActivationEpoch()) valBal, err := bs.GetBalance(idx) require.NoError(t, err) @@ -284,6 +285,17 @@ func checkValidatorBartio( // checks on validators common to all networks commonChecksValidators(t, cs, val, dep) + require.Equal( + t, + math.Epoch(constants.FarFutureEpoch), + val.GetActivationEligibilityEpoch(), + ) + require.Equal( + t, + math.Epoch(constants.FarFutureEpoch), + val.GetActivationEpoch(), + ) + // Bartio specific checks on validators valBal, err := bs.GetBalance(idx) require.NoError(t, err) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index 953646ab7..aedeca36e 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -49,10 +49,20 @@ func (sp *StateProcessor[ minEffectiveBalance := math.Gwei(sp.cs.EjectionBalance() + sp.cs.EffectiveBalanceIncrement()) + // We do not currently have a cap on validator churn, + // so we can process validators in a single loop var idx math.ValidatorIndex for si, val := range vals { + valModified := false if val.IsEligibleForActivationQueue(minEffectiveBalance) { val.SetActivationEligibilityEpoch(nextEpoch) + valModified = true + } + if val.IsEligibleForActivation(currEpoch) { + val.SetActivationEpoch(nextEpoch) + valModified = true + } + if valModified { idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) if err != nil { return fmt.Errorf("registry update, failed loading validator index, state index %d: %w", si, err) diff --git a/state-transition/core/types.go b/state-transition/core/types.go index 9412acb90..f56ba84ea 100644 --- a/state-transition/core/types.go +++ b/state-transition/core/types.go @@ -244,6 +244,8 @@ type Validator[ IsSlashed() bool IsEligibleForActivationQueue(threshold math.Gwei) bool + IsEligibleForActivation(finalizedEpoch math.Epoch) bool + // GetPubkey returns the public key of the validator. GetPubkey() crypto.BLSPubkey // GetEffectiveBalance returns the effective balance of the validator in @@ -258,6 +260,9 @@ type Validator[ GetActivationEligibilityEpoch() math.Epoch SetActivationEligibilityEpoch(math.Epoch) + + GetActivationEpoch() math.Epoch + SetActivationEpoch(math.Epoch) } type Validators interface { From 763ed856042da2059d3f4a28a6762545a7ca63a9 Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 18:56:31 -0500 Subject: [PATCH 03/16] wip: adding handling for exit and withdrawable epochs --- consensus-types/types/validator.go | 26 ++-- .../core/state_processor_staking.go | 126 +----------------- .../core/state_processor_validators.go | 86 +++++++++++- state-transition/core/types.go | 10 +- 4 files changed, 110 insertions(+), 138 deletions(-) diff --git a/consensus-types/types/validator.go b/consensus-types/types/validator.go index b7dcb8e87..3396fb57e 100644 --- a/consensus-types/types/validator.go +++ b/consensus-types/types/validator.go @@ -317,16 +317,6 @@ func (v *Validator) SetEffectiveBalance(balance math.Gwei) { v.EffectiveBalance = balance } -// SetWithdrawableEpoch sets the epoch when the validator can withdraw. -func (v *Validator) SetWithdrawableEpoch(e math.Epoch) { - v.WithdrawableEpoch = e -} - -// GetWithdrawableEpoch returns the epoch when the validator can withdraw. -func (v Validator) GetWithdrawableEpoch() math.Epoch { - return v.WithdrawableEpoch -} - func (v *Validator) SetActivationEligibilityEpoch(e math.Epoch) { v.ActivationEligibilityEpoch = e } @@ -343,6 +333,22 @@ func (v *Validator) GetActivationEpoch() math.Epoch { return v.ActivationEpoch } +func (v *Validator) SetExitEpoch(e math.Epoch) { + v.ExitEpoch = e +} + +func (v Validator) GetExitEpoch() math.Epoch { + return v.ExitEpoch +} + +func (v *Validator) SetWithdrawableEpoch(e math.Epoch) { + v.WithdrawableEpoch = e +} + +func (v Validator) GetWithdrawableEpoch() math.Epoch { + return v.WithdrawableEpoch +} + // GetWithdrawalCredentials returns the withdrawal credentials of the validator. func (v Validator) GetWithdrawalCredentials() WithdrawalCredentials { return v.WithdrawalCredentials diff --git a/state-transition/core/state_processor_staking.go b/state-transition/core/state_processor_staking.go index 8c3458e71..c70b6c405 100644 --- a/state-transition/core/state_processor_staking.go +++ b/state-transition/core/state_processor_staking.go @@ -21,9 +21,6 @@ package core import ( - "bytes" - "slices" - "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/errors" "github.com/berachain/beacon-kit/primitives/common" @@ -192,8 +189,8 @@ func (sp *StateProcessor[ st BeaconStateT, dep DepositT, ) error { - var candidateVal ValidatorT - candidateVal = candidateVal.New( + var val ValidatorT + val = val.New( dep.GetPubkey(), dep.GetWithdrawalCredentials(), dep.GetAmount(), @@ -201,121 +198,6 @@ func (sp *StateProcessor[ math.Gwei(sp.cs.MaxEffectiveBalance()), ) - // BeaconKit enforces a cap on the validator set size. If the deposit - // breaches the cap, we find the validator with the smallest stake and - // mark it as withdrawable so that it will be evicted next epoch and - // its deposits returned. - - nextEpochVals, err := sp.nextEpochValidatorSet(st) - if err != nil { - return err - } - //#nosec:G701 // no overflow risk here - if uint64(len(nextEpochVals)) < sp.cs.ValidatorSetCap() { - // cap not hit, just add the validator - return sp.addValidatorInternal(st, candidateVal, dep.GetAmount()) - } - - // Adding the validator would breach the cap. Find the validator - // with the smallest stake among current and candidate validators - // and kick it out. - lowestStakeVal, err := sp.lowestStakeVal(nextEpochVals) - if err != nil { - return err - } - - slot, err := st.GetSlot() - if err != nil { - return err - } - nextEpoch := sp.cs.SlotToEpoch(slot) + 1 - - if candidateVal.GetEffectiveBalance() <= lowestStakeVal.GetEffectiveBalance() { - // in case of tie-break among candidate validator we prefer - // existing one so we mark candidate as withdrawable - // We wait next epoch to return funds, as a way to curb spamming - candidateVal.SetWithdrawableEpoch(nextEpoch) - return sp.addValidatorInternal(st, candidateVal, dep.GetAmount()) - } - - // mark existing validator for eviction and add candidate - lowestStakeVal.SetWithdrawableEpoch(nextEpoch) - idx, err := st.ValidatorIndexByPubkey(lowestStakeVal.GetPubkey()) - if err != nil { - return err - } - if err = st.UpdateValidatorAtIndex(idx, lowestStakeVal); err != nil { - return err - } - return sp.addValidatorInternal(st, candidateVal, dep.GetAmount()) -} - -// nextEpochValidatorSet returns the current estimation of what next epoch -// validator set would be. -func (sp *StateProcessor[ - _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, -]) nextEpochValidatorSet(st BeaconStateT) ([]ValidatorT, error) { - slot, err := st.GetSlot() - if err != nil { - return nil, err - } - nextEpoch := sp.cs.SlotToEpoch(slot) + 1 - - vals, err := st.GetValidators() - if err != nil { - return nil, err - } - activeVals := make([]ValidatorT, 0, len(vals)) - for _, val := range vals { - if val.GetEffectiveBalance() <= math.U64(sp.cs.EjectionBalance()) { - continue - } - if val.GetWithdrawableEpoch() == nextEpoch { - continue - } - activeVals = append(activeVals, val) - } - - return activeVals, nil -} - -// TODO: consider moving this to BeaconState directly -func (*StateProcessor[ - _, _, _, _, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, -]) lowestStakeVal(currentVals []ValidatorT) ( - ValidatorT, - error, -) { - // TODO: consider heapifying slice instead. We only care about the smallest - slices.SortFunc(currentVals, func(lhs, rhs ValidatorT) int { - var ( - val1Stake = lhs.GetEffectiveBalance() - val2Stake = rhs.GetEffectiveBalance() - ) - switch { - case val1Stake < val2Stake: - return -1 - case val1Stake > val2Stake: - return 1 - default: - // validators pks are guaranteed to be different - var ( - val1Pk = lhs.GetPubkey() - val2Pk = rhs.GetPubkey() - ) - return bytes.Compare(val1Pk[:], val2Pk[:]) - } - }) - return currentVals[0], nil -} - -func (sp *StateProcessor[ - _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, -]) addValidatorInternal( - st BeaconStateT, - val ValidatorT, - depositAmount math.Gwei, -) error { // TODO: This is a bug that lives on bArtio. Delete this eventually. if sp.cs.DepositEth1ChainID() == spec.BartioChainID { // Note in AddValidatorBartio we implicitly increase @@ -330,12 +212,12 @@ func (sp *StateProcessor[ if err != nil { return err } - if err = st.IncreaseBalance(idx, depositAmount); err != nil { + if err = st.IncreaseBalance(idx, dep.GetAmount()); err != nil { return err } sp.logger.Info( "Processed deposit to create new validator", - "deposit_amount", float64(depositAmount.Unwrap())/math.GweiPerWei, + "deposit_amount", float64(dep.GetAmount().Unwrap())/math.GweiPerWei, "validator_index", idx, "withdrawal_epoch", val.GetWithdrawableEpoch(), ) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index aedeca36e..3f68b3c70 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -21,7 +21,9 @@ package core import ( + stdbytes "bytes" "fmt" + "slices" "github.com/berachain/beacon-kit/primitives/bytes" "github.com/berachain/beacon-kit/primitives/math" @@ -29,7 +31,7 @@ import ( "github.com/sourcegraph/conc/iter" ) -//nolint:lll +//nolint:lll, gocognit // TODO fix func (sp *StateProcessor[ _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, ]) processRegistryUpdates( @@ -50,7 +52,7 @@ func (sp *StateProcessor[ minEffectiveBalance := math.Gwei(sp.cs.EjectionBalance() + sp.cs.EffectiveBalanceIncrement()) // We do not currently have a cap on validator churn, - // so we can process validators in a single loop + // so we can process validators activations in a single loop var idx math.ValidatorIndex for si, val := range vals { valModified := false @@ -62,6 +64,9 @@ func (sp *StateProcessor[ val.SetActivationEpoch(nextEpoch) valModified = true } + // Note: without slashing and voluntary withdrawals, there is no way + // for an activa validator to have its balance less or equal to EjectionBalance + if valModified { idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) if err != nil { @@ -73,6 +78,54 @@ func (sp *StateProcessor[ } } + // Enforce the validator set cap by: + // 1- retrieve validators active next epoch + // 2- sort them by stake + // 3- drop enough validators to fulfill the cap + nextEpochVals, err := sp.nextEpochValidatorSet(st) + if err != nil { + return fmt.Errorf("registry update, failed retrieving next epoch vals: %w", err) + } + + if uint64(len(nextEpochVals)) <= sp.cs.ValidatorSetCap() { + // nothing to eject + return nil + } + + slices.SortFunc(nextEpochVals, func(lhs, rhs ValidatorT) int { + var ( + val1Stake = lhs.GetEffectiveBalance() + val2Stake = rhs.GetEffectiveBalance() + ) + switch { + case val1Stake < val2Stake: + return -1 + case val1Stake > val2Stake: + return 1 + default: + // validators pks are guaranteed to be different + var ( + val1Pk = lhs.GetPubkey() + val2Pk = rhs.GetPubkey() + ) + return stdbytes.Compare(val1Pk[:], val2Pk[:]) + } + }) + + // We do not currently have a cap on validator churn, so we stop + // validators this epoch and we withdraw them next epoch + for li := range uint64(len(nextEpochVals)) - sp.cs.ValidatorSetCap() { + valToEject := nextEpochVals[li] + valToEject.SetExitEpoch(currEpoch) + valToEject.SetWithdrawableEpoch(nextEpoch) + idx, err = st.ValidatorIndexByPubkey(valToEject.GetPubkey()) + if err != nil { + return fmt.Errorf("registry update, failed loading validator index: %w", err) + } + if err = st.UpdateValidatorAtIndex(idx, valToEject); err != nil { + return fmt.Errorf("registry update, failed ejecting validator idx %d: %w", li, err) + } + } return nil } @@ -173,3 +226,32 @@ func (*StateProcessor[ } return res } + +// nextEpochValidatorSet returns the current estimation of what next epoch +// validator set would be. +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, +]) nextEpochValidatorSet(st BeaconStateT) ([]ValidatorT, error) { + slot, err := st.GetSlot() + if err != nil { + return nil, err + } + nextEpoch := sp.cs.SlotToEpoch(slot) + 1 + + vals, err := st.GetValidators() + if err != nil { + return nil, err + } + activeVals := make([]ValidatorT, 0, len(vals)) + for _, val := range vals { + if val.GetEffectiveBalance() <= math.U64(sp.cs.EjectionBalance()) { + continue + } + if val.GetWithdrawableEpoch() == nextEpoch { + continue + } + activeVals = append(activeVals, val) + } + + return activeVals, nil +} diff --git a/state-transition/core/types.go b/state-transition/core/types.go index f56ba84ea..d6cc6e9c0 100644 --- a/state-transition/core/types.go +++ b/state-transition/core/types.go @@ -253,16 +253,18 @@ type Validator[ GetEffectiveBalance() math.Gwei // SetEffectiveBalance sets the effective balance of the validator in Gwei. SetEffectiveBalance(math.Gwei) - // GetWithdrawableEpoch returns the epoch when the validator can withdraw. - GetWithdrawableEpoch() math.Epoch - // SetWithdrawableEpoch sets the epoch when the validator can withdraw. - SetWithdrawableEpoch(math.Epoch) GetActivationEligibilityEpoch() math.Epoch SetActivationEligibilityEpoch(math.Epoch) GetActivationEpoch() math.Epoch SetActivationEpoch(math.Epoch) + + GetExitEpoch() math.Epoch + SetExitEpoch(e math.Epoch) + + GetWithdrawableEpoch() math.Epoch + SetWithdrawableEpoch(math.Epoch) } type Validators interface { From 23ccf3686a2d7ba1be233d40d5994719ae9c7046 Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 19:31:15 -0500 Subject: [PATCH 04/16] wip: some more repackaging --- state-transition/core/state_processor.go | 65 ++-------------- .../core/state_processor_genesis.go | 61 +++++++++------ .../core/state_processor_rewards_penalties.go | 77 +++++++++++++++++++ .../core/state_processor_validators.go | 4 +- 4 files changed, 121 insertions(+), 86 deletions(-) create mode 100644 state-transition/core/state_processor_rewards_penalties.go diff --git a/state-transition/core/state_processor.go b/state-transition/core/state_processor.go index f67c998bb..36bde7a4d 100644 --- a/state-transition/core/state_processor.go +++ b/state-transition/core/state_processor.go @@ -30,7 +30,6 @@ import ( "github.com/berachain/beacon-kit/errors" "github.com/berachain/beacon-kit/log" "github.com/berachain/beacon-kit/primitives/common" - "github.com/berachain/beacon-kit/primitives/constants" "github.com/berachain/beacon-kit/primitives/crypto" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/transition" @@ -375,39 +374,19 @@ func (sp *StateProcessor[ ]) processEpoch( st BeaconStateT, ) (transition.ValidatorUpdates, error) { - slot, err := st.GetSlot() - if err != nil { + if err := sp.processRewardsAndPenalties(st); err != nil { return nil, err } - - switch { - case sp.cs.DepositEth1ChainID() == spec.BartioChainID: - if err = sp.hollowProcessRewardsAndPenalties(st); err != nil { - return nil, err - } - case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && - slot < math.U64(spec.BoonetFork3Height): - // We cannot simply drop hollowProcessRewardsAndPenalties because - // appHash accounts for the list of operations carried out - // over the state even if the operations does not affect the state - // (rewards and penalties are always zero at this stage of beaconKit) - if err = sp.hollowProcessRewardsAndPenalties(st); err != nil { - return nil, err - } - default: - // no real need to perform hollowProcessRewardsAndPenalties - } - - if err = sp.processRegistryUpdates(st); err != nil { + if err := sp.processRegistryUpdates(st); err != nil { return nil, err } - if err = sp.processEffectiveBalanceUpdates(st); err != nil { + if err := sp.processEffectiveBalanceUpdates(st); err != nil { return nil, err } - if err = sp.processSlashingsReset(st); err != nil { + if err := sp.processSlashingsReset(st); err != nil { return nil, err } - if err = sp.processRandaoMixesReset(st); err != nil { + if err := sp.processRandaoMixesReset(st); err != nil { return nil, err } return sp.processValidatorsSetUpdates(st) @@ -495,40 +474,6 @@ func (sp *StateProcessor[ return st.SetLatestBlockHeader(lbh) } -func (sp *StateProcessor[ - _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, _, _, _, _, _, -]) hollowProcessRewardsAndPenalties(st BeaconStateT) error { - slot, err := st.GetSlot() - if err != nil { - return err - } - - if sp.cs.SlotToEpoch(slot) == math.U64(constants.GenesisEpoch) { - return nil - } - - // this has been simplified to make clear that - // we are not really doing anything here - valCount, err := st.GetTotalValidators() - if err != nil { - return err - } - - for i := range valCount { - // Increase the balance of the validator. - if err = st.IncreaseBalance(math.ValidatorIndex(i), 0); err != nil { - return err - } - - // Decrease the balance of the validator. - if err = st.DecreaseBalance(math.ValidatorIndex(i), 0); err != nil { - return err - } - } - - return nil -} - // processEffectiveBalanceUpdates as defined in the Ethereum 2.0 specification. // https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#effective-balances-updates // diff --git a/state-transition/core/state_processor_genesis.go b/state-transition/core/state_processor_genesis.go index bce1f1fc7..0cd7bb9c8 100644 --- a/state-transition/core/state_processor_genesis.go +++ b/state-transition/core/state_processor_genesis.go @@ -34,7 +34,7 @@ import ( // InitializePreminedBeaconStateFromEth1 initializes the beacon state. // -//nolint:gocognit,funlen,lll // todo fix. +//nolint:gocognit,funlen // todo fix. func (sp *StateProcessor[ _, BeaconBlockBodyT, BeaconBlockHeaderT, BeaconStateT, _, DepositT, Eth1DataT, _, ExecutionPayloadHeaderT, ForkT, _, _, ValidatorT, _, _, _, _, @@ -106,29 +106,8 @@ func (sp *StateProcessor[ } // process activations - switch { - case sp.cs.DepositEth1ChainID() == spec.BartioChainID: - // nothing to do - case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID: - // nothing to do - default: - vals, err := st.GetValidators() - if err != nil { - return nil, fmt.Errorf("genesis activation, failed listing validators: %w", err) - } - - var idx math.ValidatorIndex - for _, val := range vals { - val.SetActivationEligibilityEpoch(0) - val.SetActivationEpoch(0) - idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) - if err != nil { - return nil, err - } - if err = st.UpdateValidatorAtIndex(idx, val); err != nil { - return nil, err - } - } + if err := sp.processGenesisActivation(st); err != nil { + return nil, err } // Handle special case bartio genesis. @@ -173,3 +152,37 @@ func (sp *StateProcessor[ return sp.processValidatorsSetUpdates(st) } + +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, +]) processGenesisActivation( + st BeaconStateT, +) error { + switch { + case sp.cs.DepositEth1ChainID() == spec.BartioChainID: + // nothing to do + return nil + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID: + // nothing to do + return nil + default: + vals, err := st.GetValidators() + if err != nil { + return fmt.Errorf("genesis activation, failed listing validators: %w", err) + } + + var idx math.ValidatorIndex + for _, val := range vals { + val.SetActivationEligibilityEpoch(0) + val.SetActivationEpoch(0) + idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) + if err != nil { + return err + } + if err = st.UpdateValidatorAtIndex(idx, val); err != nil { + return err + } + } + return nil + } +} diff --git a/state-transition/core/state_processor_rewards_penalties.go b/state-transition/core/state_processor_rewards_penalties.go new file mode 100644 index 000000000..cf9b96255 --- /dev/null +++ b/state-transition/core/state_processor_rewards_penalties.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2024, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package core + +import ( + "github.com/berachain/beacon-kit/config/spec" + "github.com/berachain/beacon-kit/primitives/constants" + "github.com/berachain/beacon-kit/primitives/math" +) + +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, _, _, _, _, _, +]) processRewardsAndPenalties(st BeaconStateT) error { + slot, err := st.GetSlot() + if err != nil { + return err + } + + // processRewardsAndPenalties does not really do anything right now. + // However we cannot simply drop it because appHash accounts + // for the list of operations carried out over the state + // even if the operations does not affect the final state + // (rewards and penalties are always zero at this stage of beaconKit) + + switch { + case sp.cs.DepositEth1ChainID() == spec.BartioChainID: + // go head doing the processing, eve + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot < math.U64(spec.BoonetFork3Height): + default: + // no real need to perform hollowProcessRewardsAndPenalties + return nil + } + + if sp.cs.SlotToEpoch(slot) == math.U64(constants.GenesisEpoch) { + return nil + } + + // this has been simplified to make clear that + // we are not really doing anything here + valCount, err := st.GetTotalValidators() + if err != nil { + return err + } + + for i := range valCount { + // Increase the balance of the validator. + if err = st.IncreaseBalance(math.ValidatorIndex(i), 0); err != nil { + return err + } + + // Decrease the balance of the validator. + if err = st.DecreaseBalance(math.ValidatorIndex(i), 0); err != nil { + return err + } + } + + return nil +} diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index 3f68b3c70..0d692a111 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -116,8 +116,8 @@ func (sp *StateProcessor[ // validators this epoch and we withdraw them next epoch for li := range uint64(len(nextEpochVals)) - sp.cs.ValidatorSetCap() { valToEject := nextEpochVals[li] - valToEject.SetExitEpoch(currEpoch) - valToEject.SetWithdrawableEpoch(nextEpoch) + valToEject.SetExitEpoch(nextEpoch) + valToEject.SetWithdrawableEpoch(nextEpoch + 1) idx, err = st.ValidatorIndexByPubkey(valToEject.GetPubkey()) if err != nil { return fmt.Errorf("registry update, failed loading validator index: %w", err) From 0c038460101b21129804d3834fe3dc54f35ac51d Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 19:55:34 -0500 Subject: [PATCH 05/16] some UT code consolidation --- .../core/state_processor_staking_test.go | 176 +++++------------- 1 file changed, 49 insertions(+), 127 deletions(-) diff --git a/state-transition/core/state_processor_staking_test.go b/state-transition/core/state_processor_staking_test.go index 439c112b7..28a10fed4 100644 --- a/state-transition/core/state_processor_staking_test.go +++ b/state-transition/core/state_processor_staking_test.go @@ -143,31 +143,7 @@ func TestTransitionUpdateValidators(t *testing.T) { require.Equal(t, uint64(len(genDeposits)), latestValIdx) // STEP 2: check that effective balance is updated once next epoch arrives - var blk = blk1 - currEpoch := cs.SlotToEpoch(blk.GetSlot()) - for currEpoch == cs.SlotToEpoch(blk.GetSlot()+1) { - blk = buildNextBlock( - t, - st, - &types.BeaconBlockBody{ - ExecutionPayload: &types.ExecutionPayload{ - Timestamp: blk.Body.ExecutionPayload.Timestamp + 1, - ExtraData: []byte("testing"), - Transactions: [][]byte{}, - Withdrawals: []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(), - }, - BaseFeePerGas: math.NewU256(0), - }, - Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{}, - }, - ) - - updatedVals, err = sp.Transition(ctx, st, blk) - require.NoError(t, err) - require.Empty(t, updatedVals) // validators set updates only at epoch - } + blk := moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) // finally the block turning epoch blk = buildNextBlock( @@ -300,31 +276,7 @@ func TestTransitionCreateValidator(t *testing.T) { require.Equal(t, uint64(len(genDeposits)), latestValIdx) // STEP 2: check that effective balance is updated once next epoch arrives - var blk = blk1 - currEpoch := cs.SlotToEpoch(blk.GetSlot()) - for currEpoch == cs.SlotToEpoch(blk.GetSlot()+1) { - blk = buildNextBlock( - t, - st, - &types.BeaconBlockBody{ - ExecutionPayload: &types.ExecutionPayload{ - Timestamp: blk.Body.ExecutionPayload.Timestamp + 1, - ExtraData: []byte("testing"), - Transactions: [][]byte{}, - Withdrawals: []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(), - }, - BaseFeePerGas: math.NewU256(0), - }, - Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{}, - }, - ) - - updatedVals, err = sp.Transition(ctx, st, blk) - require.NoError(t, err) - require.Empty(t, updatedVals) // validators set updates only at epoch - } + blk := moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) // finally the block turning epoch blk = buildNextBlock( @@ -679,6 +631,7 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { extraVal, err := st.ValidatorByIndex(extraValIdx) require.NoError(t, err) require.Equal(t, extraValDeposit.Pubkey, extraVal.Pubkey) + require.Equal(t, math.Slot(1), extraVal.ExitEpoch) require.Equal(t, math.Slot(1), extraVal.WithdrawableEpoch) extraValBalance, err := st.GetBalance(extraValIdx) @@ -687,34 +640,12 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { // STEP 2: move the chain to the next epoch and show withdrawals // for rejected validator are enqueuued then - blk := blk1 - currEpoch := cs.SlotToEpoch(blk.GetSlot()) - for currEpoch == cs.SlotToEpoch(blk.GetSlot()+1) { - blk = buildNextBlock( - t, - st, - &types.BeaconBlockBody{ - ExecutionPayload: &types.ExecutionPayload{ - Timestamp: blk.Body.ExecutionPayload.Timestamp + 1, - ExtraData: []byte("testing"), - Transactions: [][]byte{}, - Withdrawals: []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(), - }, - BaseFeePerGas: math.NewU256(0), - }, - Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{}, - }, - ) - - _, err = sp.Transition(ctx, st, blk) - require.NoError(t, err) - } + _ = moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) + // finally the block turning epoch extraValAddr, err := extraValCreds.ToExecutionAddress() require.NoError(t, err) - blk = buildNextBlock( + blk := buildNextBlock( t, st, &types.BeaconBlockBody{ @@ -860,33 +791,10 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { // STEP 2: move chain to next epoch to see extra validator // be activated and withdraws for evicted validator - blk := blk1 - currEpoch := cs.SlotToEpoch(blk.GetSlot()) - for currEpoch == cs.SlotToEpoch(blk.GetSlot()+1) { - blk = buildNextBlock( - t, - st, - &types.BeaconBlockBody{ - ExecutionPayload: &types.ExecutionPayload{ - Timestamp: blk.Body.ExecutionPayload.Timestamp + 1, - ExtraData: []byte("testing"), - Transactions: [][]byte{}, - Withdrawals: []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(), - }, - BaseFeePerGas: math.NewU256(0), - }, - Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{}, - }, - ) + _ = moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) - vals, err = sp.Transition(ctx, st, blk) - require.NoError(t, err) - require.Empty(t, vals) // no vals changes expected before next epoch - } - - blk = buildNextBlock( + // finally the block turning epoch + blk := buildNextBlock( t, st, &types.BeaconBlockBody{ @@ -1099,33 +1007,10 @@ func TestTransitionValidatorCap_DoubleEviction(t *testing.T) { ) // STEP 3: move to next epoch - blk := blk2 - currEpoch := cs.SlotToEpoch(blk.GetSlot()) - for currEpoch == cs.SlotToEpoch(blk.GetSlot()+1) { - blk = buildNextBlock( - t, - st, - &types.BeaconBlockBody{ - ExecutionPayload: &types.ExecutionPayload{ - Timestamp: blk.Body.ExecutionPayload.Timestamp + 1, - ExtraData: []byte("testing"), - Transactions: [][]byte{}, - Withdrawals: []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(), - }, - BaseFeePerGas: math.NewU256(0), - }, - Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{}, - }, - ) - - vals, err = sp.Transition(ctx, st, blk) - require.NoError(t, err) - require.Empty(t, vals) // no vals changes expected before next epoch - } + _ = moveToEndOfEpoch(t, blk2, cs, sp, st, ctx) - blk = buildNextBlock( + // finally the block turning epoch + blk := buildNextBlock( t, st, &types.BeaconBlockBody{ @@ -1211,3 +1096,40 @@ func generateTestPK(t *testing.T, rndSeed int) (bytes.B48, int) { rndSeed++ return key, rndSeed } + +func moveToEndOfEpoch( + t *testing.T, + tip *types.BeaconBlock, + cs chain.Spec[bytes.B4, math.U64, common.ExecutionAddress, math.U64, any], + sp *TestStateProcessorT, + st *TestBeaconStateT, + ctx *transition.Context, +) *types.BeaconBlock { + t.Helper() + blk := tip + currEpoch := cs.SlotToEpoch(blk.GetSlot()) + for currEpoch == cs.SlotToEpoch(blk.GetSlot()+1) { + blk = buildNextBlock( + t, + st, + &types.BeaconBlockBody{ + ExecutionPayload: &types.ExecutionPayload{ + Timestamp: blk.Body.ExecutionPayload.Timestamp + 1, + ExtraData: []byte("testing"), + Transactions: [][]byte{}, + Withdrawals: []*engineprimitives.Withdrawal{ + st.EVMInflationWithdrawal(), + }, + BaseFeePerGas: math.NewU256(0), + }, + Eth1Data: &types.Eth1Data{}, + Deposits: []*types.Deposit{}, + }, + ) + + vals, err := sp.Transition(ctx, st, blk) + require.NoError(t, err) + require.Empty(t, vals) // no vals changes expected before next epoch + } + return blk +} From 574099e7f7625c7493be1b51de6eed0abbe62dba Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 22:45:51 -0500 Subject: [PATCH 06/16] wip: fixing UTs --- .../core/state_processor_staking_test.go | 93 ++++++++++++++++--- .../core/state_processor_validators.go | 3 + 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/state-transition/core/state_processor_staking_test.go b/state-transition/core/state_processor_staking_test.go index 28a10fed4..2593698bf 100644 --- a/state-transition/core/state_processor_staking_test.go +++ b/state-transition/core/state_processor_staking_test.go @@ -544,13 +544,17 @@ func TestTransitionMaxWithdrawals(t *testing.T) { // TestTransitionHittingValidatorsCap shows that the extra // validator added when validators set is at cap gets never activated // and its deposit is returned at after next epoch starts. +// +//nolint:lll // let it be func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { cs := setupChain(t, components.BetnetChainSpecType) sp, st, ds, ctx := setupState(t, cs) var ( - maxBalance = math.Gwei(cs.MaxEffectiveBalance()) - rndSeed = 2024 // seed used to generate unique random value + maxBalance = math.Gwei(cs.MaxEffectiveBalance()) + ejectionBalance = math.Gwei(cs.EjectionBalance()) + minBalance = ejectionBalance + math.Gwei(cs.EffectiveBalanceIncrement()) + rndSeed = 2024 // seed used to generate unique random value ) // STEP 0: Setup genesis with GetValidatorSetCap validators @@ -595,7 +599,7 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { extraValDeposit = &types.Deposit{ Pubkey: extraValKey, Credentials: extraValCreds, - Amount: maxBalance, + Amount: minBalance, Index: uint64(len(genDeposits)), } ) @@ -625,27 +629,94 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { _, err = sp.Transition(ctx, st, blk1) require.NoError(t, err) - // check extra validator is added with Withdraw epoch duly set extraValIdx, err := st.ValidatorIndexByPubkey(extraValDeposit.Pubkey) require.NoError(t, err) extraVal, err := st.ValidatorByIndex(extraValIdx) require.NoError(t, err) require.Equal(t, extraValDeposit.Pubkey, extraVal.Pubkey) - require.Equal(t, math.Slot(1), extraVal.ExitEpoch) - require.Equal(t, math.Slot(1), extraVal.WithdrawableEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch) + + // STEP 2: move the chain to the next epoch and show that + // the extra validator is eligible for activation + _ = moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) - extraValBalance, err := st.GetBalance(extraValIdx) + // finally the block turning epoch + blk := buildNextBlock( + t, + st, + &types.BeaconBlockBody{ + ExecutionPayload: &types.ExecutionPayload{ + Timestamp: blk1.Body.ExecutionPayload.Timestamp + 1, + ExtraData: []byte("testing"), + Transactions: [][]byte{}, + Withdrawals: []*engineprimitives.Withdrawal{ + st.EVMInflationWithdrawal(), + }, + BaseFeePerGas: math.NewU256(0), + }, + Eth1Data: &types.Eth1Data{}, + Deposits: []*types.Deposit{}, + }, + ) + + // run the test + _, err = sp.Transition(ctx, st, blk) + require.NoError(t, err) + + // check extra validator is added with Withdraw epoch duly set + extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) - require.Equal(t, extraValDeposit.Amount, extraValBalance) + require.Equal(t, math.Epoch(1), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch) - // STEP 2: move the chain to the next epoch and show withdrawals + // STEP 3: move the chain to the next epoch and show that the extra validator + // is activate and immediately marked for exit + _ = moveToEndOfEpoch(t, blk, cs, sp, st, ctx) + + // finally the block turning epoch + blk = buildNextBlock( + t, + st, + &types.BeaconBlockBody{ + ExecutionPayload: &types.ExecutionPayload{ + Timestamp: blk1.Body.ExecutionPayload.Timestamp + 1, + ExtraData: []byte("testing"), + Transactions: [][]byte{}, + Withdrawals: []*engineprimitives.Withdrawal{ + st.EVMInflationWithdrawal(), + }, + BaseFeePerGas: math.NewU256(0), + }, + Eth1Data: &types.Eth1Data{}, + Deposits: []*types.Deposit{}, + }, + ) + + // run the test + _, err = sp.Transition(ctx, st, blk) + require.NoError(t, err) + + extraVal, err = st.ValidatorByIndex(extraValIdx) + require.NoError(t, err) + require.Equal(t, extraValDeposit.Pubkey, extraVal.Pubkey) + require.Equal(t, math.Epoch(1), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(2), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(2), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(3), extraVal.WithdrawableEpoch) + + // STEP 4: move the chain to the next epoch and show withdrawals // for rejected validator are enqueuued then - _ = moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) + _ = moveToEndOfEpoch(t, blk, cs, sp, st, ctx) // finally the block turning epoch extraValAddr, err := extraValCreds.ToExecutionAddress() require.NoError(t, err) - blk := buildNextBlock( + blk = buildNextBlock( t, st, &types.BeaconBlockBody{ diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index 0d692a111..3d8c895ce 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -247,6 +247,9 @@ func (sp *StateProcessor[ if val.GetEffectiveBalance() <= math.U64(sp.cs.EjectionBalance()) { continue } + if val.GetActivationEligibilityEpoch() == nextEpoch { + continue + } if val.GetWithdrawableEpoch() == nextEpoch { continue } From 1cd77bff6e0dcb778b66e676a30bdcbde72e2d49 Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 22:55:27 -0500 Subject: [PATCH 07/16] simplified slashing reset --- .../core/state_processor_slashing.go | 113 +++--------------- 1 file changed, 17 insertions(+), 96 deletions(-) diff --git a/state-transition/core/state_processor_slashing.go b/state-transition/core/state_processor_slashing.go index 0af3a7957..82d10a03d 100644 --- a/state-transition/core/state_processor_slashing.go +++ b/state-transition/core/state_processor_slashing.go @@ -21,6 +21,7 @@ package core import ( + "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/primitives/math" ) @@ -33,108 +34,28 @@ func (sp *StateProcessor[ ]) processSlashingsReset( st BeaconStateT, ) error { - // Get the current epoch. - slot, err := st.GetSlot() - if err != nil { - return err - } - - index := (sp.cs.SlotToEpoch(slot).Unwrap() + 1) % sp.cs.EpochsPerSlashingsVector() - return st.UpdateSlashingAtIndex(index, 0) -} - -// processProposerSlashing as defined in the Ethereum 2.0 specification. -// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#proposer-slashings -// -//nolint:lll,unused // will be used later -func (sp *StateProcessor[ - _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, _, _, _, _, _, -]) processProposerSlashing( - _ BeaconStateT, - // ps ProposerSlashing, -) error { - return nil -} - -// processSlashings as defined in the Ethereum 2.0 specification. -// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#slashings -// -// processSlashings processes the slashings and ensures they match the local -// state. -// -//nolint:lll,unused // will be used later -func (sp *StateProcessor[ - _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, _, _, _, _, _, -]) processSlashings( - st BeaconStateT, -) error { - totalBalance, err := st.GetTotalActiveBalances(sp.cs.SlotsPerEpoch()) - if err != nil { - return err - } - - totalSlashings, err := st.GetTotalSlashing() - if err != nil { - return err - } - - adjustedTotalSlashingBalance := min( - totalSlashings.Unwrap()*sp.cs.ProportionalSlashingMultiplier(), - totalBalance.Unwrap(), - ) + // processSlashingsReset does not really do anything right now. + // However we cannot simply drop it because appHash accounts + // for the list of operations carried out over the state + // even if the operations does not affect the final state + // (currently no slashing on beaconKit) - vals, err := st.GetValidators() - if err != nil { - return err - } - - // Get the current slot. slot, err := st.GetSlot() if err != nil { return err } - //nolint:mnd // this is in the spec - slashableEpoch := (sp.cs.SlotToEpoch(slot).Unwrap() + sp.cs.EpochsPerSlashingsVector()) / 2 - - // Iterate through the validators and slash if needed. - for _, val := range vals { - if val.IsSlashed() && - (slashableEpoch == val.GetWithdrawableEpoch().Unwrap()) { - if err = sp.processSlash( - st, val, - adjustedTotalSlashingBalance, - totalBalance.Unwrap(), - ); err != nil { - return err - } - } + switch { + case sp.cs.DepositEth1ChainID() == spec.BartioChainID: + // go head doing the processing + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot < math.U64(spec.BoonetFork3Height): + // go head doing the processing + default: + // no real need to perform slashing reset + return nil } - return nil -} -// processSlash handles the logic for slashing a validator. -// -//nolint:unused // will be used later -func (sp *StateProcessor[ - _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, -]) processSlash( - st BeaconStateT, - val ValidatorT, - adjustedTotalSlashingBalance uint64, - totalBalance uint64, -) error { - // Calculate the penalty. - increment := sp.cs.EffectiveBalanceIncrement() - balDivIncrement := val.GetEffectiveBalance().Unwrap() / increment - penaltyNumerator := balDivIncrement * adjustedTotalSlashingBalance - penalty := penaltyNumerator / totalBalance * increment - - // Get the val index and decrease the balance of the validator. - idx, err := st.ValidatorIndexByPubkey(val.GetPubkey()) - if err != nil { - return err - } - - return st.DecreaseBalance(idx, math.Gwei(penalty)) + index := (sp.cs.SlotToEpoch(slot).Unwrap() + 1) % sp.cs.EpochsPerSlashingsVector() + return st.UpdateSlashingAtIndex(index, 0) } From 712c4bf97ca51d83dfefd04dc63b6b38c2e4f7bc Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 23:18:40 -0500 Subject: [PATCH 08/16] wip: some more UTs fixing --- .../core/state_processor_staking_test.go | 358 ++++++------------ 1 file changed, 118 insertions(+), 240 deletions(-) diff --git a/state-transition/core/state_processor_staking_test.go b/state-transition/core/state_processor_staking_test.go index 2593698bf..ffe2d7f0b 100644 --- a/state-transition/core/state_processor_staking_test.go +++ b/state-transition/core/state_processor_staking_test.go @@ -117,9 +117,9 @@ func TestTransitionUpdateValidators(t *testing.T) { require.NoError(t, ds.EnqueueDeposits(blk1.Body.Deposits)) // run the test - updatedVals, err := sp.Transition(ctx, st, blk1) + valDiff, err := sp.Transition(ctx, st, blk1) require.NoError(t, err) - require.Empty(t, updatedVals) // validators set updates only at epoch turn + require.Empty(t, valDiff) // validators set updates only at epoch turn // check validator balances are duly updated, that is: // - balance is updated immediately @@ -164,9 +164,9 @@ func TestTransitionUpdateValidators(t *testing.T) { }, ) - newEpochVals, err := sp.Transition(ctx, st, blk) + valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) - require.Len(t, newEpochVals, 1) // just topped up one validator + require.Len(t, valDiff, 1) // just topped up one validator expectedBalance = genDeposits[2].Amount + blkDeposit.Amount expectedEffectiveBalance = expectedBalance @@ -182,6 +182,8 @@ func TestTransitionUpdateValidators(t *testing.T) { // TestTransitionCreateValidator shows the lifecycle // of a validator creation. +// +//nolint:lll // let it be func TestTransitionCreateValidator(t *testing.T) { // Create state processor to test cs := setupChain(t, components.BetnetChainSpecType) @@ -275,7 +277,8 @@ func TestTransitionCreateValidator(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(len(genDeposits)), latestValIdx) - // STEP 2: check that effective balance is updated once next epoch arrives + // STEP 2: move the chain to the next epoch and show that + // the extra validator is eligible for activation blk := moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) // finally the block turning epoch @@ -297,9 +300,52 @@ func TestTransitionCreateValidator(t *testing.T) { }, ) - newEpochVals, err := sp.Transition(ctx, st, blk) + valDiff, err := sp.Transition(ctx, st, blk) + require.NoError(t, err) + require.Empty(t, valDiff) // new validator is only eligible for activation + + extraValIdx, err := st.ValidatorIndexByPubkey(blkDeposit.Pubkey) + require.NoError(t, err) + extraVal, err := st.ValidatorByIndex(extraValIdx) + require.NoError(t, err) + require.Equal(t, math.Epoch(1), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch) + + // STEP 3: move the chain to the next epoch and show that + // the extra validator is activate + _ = moveToEndOfEpoch(t, blk, cs, sp, st, ctx) + + // finally the block turning epoch + blk = buildNextBlock( + t, + st, + &types.BeaconBlockBody{ + ExecutionPayload: &types.ExecutionPayload{ + Timestamp: blk1.Body.ExecutionPayload.Timestamp + 1, + ExtraData: []byte("testing"), + Transactions: [][]byte{}, + Withdrawals: []*engineprimitives.Withdrawal{ + st.EVMInflationWithdrawal(), + }, + BaseFeePerGas: math.NewU256(0), + }, + Eth1Data: &types.Eth1Data{}, + Deposits: []*types.Deposit{}, + }, + ) + + // run the test + _, err = sp.Transition(ctx, st, blk) + require.NoError(t, err) + + extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) - require.Len(t, newEpochVals, 1) // just added 1 validator + require.Equal(t, math.Epoch(1), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(2), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch) expectedBalance = blkDeposit.Amount expectedEffectiveBalance = expectedBalance @@ -633,7 +679,6 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { require.NoError(t, err) extraVal, err := st.ValidatorByIndex(extraValIdx) require.NoError(t, err) - require.Equal(t, extraValDeposit.Pubkey, extraVal.Pubkey) require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEligibilityEpoch) require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEpoch) require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) @@ -703,14 +748,13 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) - require.Equal(t, extraValDeposit.Pubkey, extraVal.Pubkey) require.Equal(t, math.Epoch(1), extraVal.ActivationEligibilityEpoch) require.Equal(t, math.Epoch(2), extraVal.ActivationEpoch) require.Equal(t, math.Epoch(2), extraVal.ExitEpoch) require.Equal(t, math.Epoch(3), extraVal.WithdrawableEpoch) // STEP 4: move the chain to the next epoch and show withdrawals - // for rejected validator are enqueuued then + // for rejected validator are enqueued then _ = moveToEndOfEpoch(t, blk, cs, sp, st, ctx) // finally the block turning epoch @@ -748,15 +792,17 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { // TestTransitionHittingValidatorsCap shows that if the extra // validator added when validators set is at cap improves amount staked // an existing validator is removed at the beginning of next epoch. +// +//nolint:lll // let it be func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { cs := setupChain(t, components.BetnetChainSpecType) sp, st, ds, ctx := setupState(t, cs) var ( - maxBalance = math.Gwei(cs.MaxEffectiveBalance()) - increment = math.Gwei(cs.EffectiveBalanceIncrement()) - minBalance = math.Gwei(cs.EjectionBalance()) - rndSeed = 2024 // seed used to generate unique random value + maxBalance = math.Gwei(cs.MaxEffectiveBalance()) + ejectionBalance = math.Gwei(cs.EjectionBalance()) + minBalance = ejectionBalance + math.Gwei(cs.EffectiveBalanceIncrement()) + rndSeed = 2024 // seed used to generate unique random value ) // STEP 0: Setup genesis with GetValidatorSetCap validators @@ -786,13 +832,10 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { ) } // make a deposit small to be ready for eviction - genDeposits[0].Amount = minBalance + increment - smallestVal := genDeposits[0] - smallestValAddr, err := genDeposits[0].Credentials.ToExecutionAddress() - require.NoError(t, err) + genDeposits[0].Amount = minBalance var genVals transition.ValidatorUpdates - genVals, err = sp.InitializePreminedBeaconStateFromEth1( + genVals, err := sp.InitializePreminedBeaconStateFromEth1( st, genDeposits, genPayloadHeader, @@ -835,33 +878,29 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { require.NoError(t, ds.EnqueueDeposits(blk1.Body.Deposits)) // run the test - var vals transition.ValidatorUpdates - vals, err = sp.Transition(ctx, st, blk1) + _, err = sp.Transition(ctx, st, blk1) require.NoError(t, err) - require.Empty(t, vals) // no vals changes expected before next epoch - // check smallest validator is updated with Withdraw epoch duly set - smallValIdx, err := st.ValidatorIndexByPubkey(smallestVal.Pubkey) - require.NoError(t, err) - smallVal, err := st.ValidatorByIndex(smallValIdx) + extraValIdx, err := st.ValidatorIndexByPubkey(extraValDeposit.Pubkey) require.NoError(t, err) - require.Equal(t, math.Slot(1), smallVal.WithdrawableEpoch) - - smallestValBalance, err := st.GetBalance(smallValIdx) + extraVal, err := st.ValidatorByIndex(extraValIdx) require.NoError(t, err) - require.Equal(t, smallestVal.Amount, smallestValBalance) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch) - // check that extra validator is added - extraValIdx, err := st.ValidatorIndexByPubkey(extraValKey) + smallestValIdx, err := st.ValidatorIndexByPubkey(genDeposits[0].Pubkey) require.NoError(t, err) - extraVal, err := st.ValidatorByIndex(extraValIdx) + smallestVal, err := st.ValidatorByIndex(smallestValIdx) require.NoError(t, err) - require.Equal(t, - math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch, - ) + require.Equal(t, math.Epoch(0), smallestVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(0), smallestVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), smallestVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), smallestVal.WithdrawableEpoch) - // STEP 2: move chain to next epoch to see extra validator - // be activated and withdraws for evicted validator + // STEP 2: move the chain to the next epoch and show that + // the extra validator is eligible for activation _ = moveToEndOfEpoch(t, blk1, cs, sp, st, ctx) // finally the block turning epoch @@ -875,12 +914,6 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { Transactions: [][]byte{}, Withdrawals: []*engineprimitives.Withdrawal{ st.EVMInflationWithdrawal(), - { - Index: 0, - Validator: smallValIdx, - Address: smallestValAddr, - Amount: smallestVal.Amount, - }, }, BaseFeePerGas: math.NewU256(0), }, @@ -889,149 +922,31 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { }, ) - vals, err = sp.Transition(ctx, st, blk) - require.NoError(t, err) - require.LessOrEqual(t, uint64(len(vals)), cs.ValidatorSetCap()) - require.Len(t, vals, 2) // just replaced one validator - - // check that we added the incoming validator at the epoch turn - require.Equal(t, extraVal.Pubkey, vals[0].Pubkey) - require.Equal(t, extraVal.EffectiveBalance, vals[0].EffectiveBalance) - - // check that we removed the smallest validator at the epoch turn - require.Equal(t, smallVal.Pubkey, vals[1].Pubkey) - require.Equal(t, math.Gwei(0), vals[1].EffectiveBalance) -} - -// show that eviction mechanism works fine even if multiple evictions -// happen in the same epoch. -// -// //nolint:maintidx // TODO: simplify -func TestTransitionValidatorCap_DoubleEviction(t *testing.T) { - cs := setupChain(t, components.BetnetChainSpecType) - sp, st, ds, ctx := setupState(t, cs) - - var ( - maxBalance = math.Gwei(cs.MaxEffectiveBalance()) - increment = math.Gwei(cs.EffectiveBalanceIncrement()) - minBalance = math.Gwei(cs.EjectionBalance()) - rndSeed = 2024 // seed used to generate unique random value - ) - - // STEP 0: fill genesis with validators till cap. Let two of them - // have smaller balance than others, so to be amenable for eviction. - var ( - genDeposits = make([]*types.Deposit, 0, cs.ValidatorSetCap()) - genPayloadHeader = new(types.ExecutionPayloadHeader).Empty() - genVersion = version.FromUint32[common.Version](version.Deneb) - ) - - // let genesis define all available validators - for idx := range cs.ValidatorSetCap() { - var ( - key bytes.B48 - creds types.WithdrawalCredentials - ) - key, rndSeed = generateTestPK(t, rndSeed) - creds, rndSeed = generateTestExecutionAddress(t, rndSeed) - - genDeposits = append(genDeposits, - &types.Deposit{ - Pubkey: key, - Credentials: creds, - Amount: maxBalance, - Index: idx, - }, - ) - } - // make a deposit small to be ready for eviction - genDeposits[0].Amount = minBalance + increment - smallest1Val := genDeposits[0] - smallest1ValAddr, err := genDeposits[0].Credentials.ToExecutionAddress() - require.NoError(t, err) - - genDeposits[1].Amount = minBalance + 2*increment - smallestVal2 := genDeposits[1] - smallestVal2Addr, err := genDeposits[1].Credentials.ToExecutionAddress() - require.NoError(t, err) - - var genVals transition.ValidatorUpdates - genVals, err = sp.InitializePreminedBeaconStateFromEth1( - st, - genDeposits, - genPayloadHeader, - genVersion, - ) - require.NoError(t, err) - require.Len(t, genVals, len(genDeposits)) - - // STEP 1: Add an extra validator - extraVal1Key, rndSeed := generateTestPK(t, rndSeed) - extraVal1Creds, rndSeed := generateTestExecutionAddress(t, rndSeed) - extraValDeposit1 := &types.Deposit{ - Pubkey: extraVal1Key, - Credentials: extraVal1Creds, - Amount: maxBalance - increment, - Index: uint64(len(genDeposits)), - } - - blk1 := buildNextBlock( - t, - st, - &types.BeaconBlockBody{ - ExecutionPayload: &types.ExecutionPayload{ - Timestamp: 10, - ExtraData: []byte("testing"), - Transactions: [][]byte{}, - Withdrawals: []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(), - }, - BaseFeePerGas: math.NewU256(0), - }, - Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{extraValDeposit1}, - }, - ) - - // make sure included deposit is already available in deposit store - require.NoError(t, ds.EnqueueDeposits(blk1.Body.Deposits)) - // run the test - vals, err := sp.Transition(ctx, st, blk1) - require.NoError(t, err) - require.Empty(t, vals) // no vals changes expected before next epoch - - // check the smallest validator Withdraw epoch is updated - smallVal1Idx, err := st.ValidatorIndexByPubkey(smallest1Val.Pubkey) - require.NoError(t, err) - smallVal1, err := st.ValidatorByIndex(smallVal1Idx) + _, err = sp.Transition(ctx, st, blk) require.NoError(t, err) - require.Equal(t, math.Slot(1), smallVal1.WithdrawableEpoch) - smallVal1Balance, err := st.GetBalance(smallVal1Idx) + // check extra validator is added with Withdraw epoch duly set + extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) - require.Equal(t, smallest1Val.Amount, smallVal1Balance) + require.Equal(t, math.Epoch(1), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch) - // check that extra validator is added - extraVal1Idx, err := st.ValidatorIndexByPubkey(extraVal1Key) - require.NoError(t, err) - extraVal1, err := st.ValidatorByIndex(extraVal1Idx) + smallestVal, err = st.ValidatorByIndex(smallestValIdx) require.NoError(t, err) - require.Equal(t, - math.Epoch(constants.FarFutureEpoch), extraVal1.WithdrawableEpoch, - ) + require.Equal(t, math.Epoch(0), smallestVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(0), smallestVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), smallestVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), smallestVal.WithdrawableEpoch) - // STEP 2: add a second, large deposit to evict second smallest validator - extraVal2Key, rndSeed := generateTestPK(t, rndSeed) - extraVal2Creds, _ := generateTestExecutionAddress(t, rndSeed) - extraVal2Deposit := &types.Deposit{ - Pubkey: extraVal2Key, - Credentials: extraVal2Creds, - Amount: maxBalance, - Index: uint64(len(genDeposits) + 1), - } + // STEP 3: move the chain to the next epoch and show that the extra validator + // is activate and genesis validator immediately marked for exit + _ = moveToEndOfEpoch(t, blk, cs, sp, st, ctx) - blk2 := buildNextBlock( + // finally the block turning epoch + blk = buildNextBlock( t, st, &types.BeaconBlockBody{ @@ -1045,43 +960,38 @@ func TestTransitionValidatorCap_DoubleEviction(t *testing.T) { BaseFeePerGas: math.NewU256(0), }, Eth1Data: &types.Eth1Data{}, - Deposits: []*types.Deposit{extraVal2Deposit}, + Deposits: []*types.Deposit{}, }, ) - // make sure included deposit is already available in deposit store - require.NoError(t, ds.EnqueueDeposits(blk2.Body.Deposits)) - // run the test - vals, err = sp.Transition(ctx, st, blk2) + _, err = sp.Transition(ctx, st, blk) require.NoError(t, err) - require.Empty(t, vals) // no vals changes expected before next epoch - // check the second smallest validator Withdraw epoch is updated - smallVal2Idx, err := st.ValidatorIndexByPubkey(smallestVal2.Pubkey) - require.NoError(t, err) - smallVal2, err := st.ValidatorByIndex(smallVal2Idx) + extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) - require.Equal(t, math.Slot(1), smallVal2.WithdrawableEpoch) + require.Equal(t, math.Epoch(1), extraVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(2), extraVal.ActivationEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.ExitEpoch) + require.Equal(t, math.Epoch(constants.FarFutureEpoch), extraVal.WithdrawableEpoch) - smallVal2Balance, err := st.GetBalance(smallVal2Idx) + smallestVal, err = st.ValidatorByIndex(smallestValIdx) require.NoError(t, err) - require.Equal(t, smallestVal2.Amount, smallVal2Balance) + require.Equal(t, math.Epoch(0), smallestVal.ActivationEligibilityEpoch) + require.Equal(t, math.Epoch(0), smallestVal.ActivationEpoch) + require.Equal(t, math.Epoch(2), smallestVal.ExitEpoch) + require.Equal(t, math.Epoch(3), smallestVal.WithdrawableEpoch) - // check that extra validator is added - extraVal2Idx, err := st.ValidatorIndexByPubkey(extraVal2Key) - require.NoError(t, err) - extraVal2, err := st.ValidatorByIndex(extraVal2Idx) - require.NoError(t, err) - require.Equal(t, - math.Epoch(constants.FarFutureEpoch), extraVal2.WithdrawableEpoch, - ) + // STEP 4: move the chain to the next epoch and show withdrawal + // for rejected validator is enqueued + _ = moveToEndOfEpoch(t, blk, cs, sp, st, ctx) - // STEP 3: move to next epoch - _ = moveToEndOfEpoch(t, blk2, cs, sp, st, ctx) + valToEvict := genDeposits[0] + valToEvictAddr, err := valToEvict.Credentials.ToExecutionAddress() + require.NoError(t, err) // finally the block turning epoch - blk := buildNextBlock( + blk = buildNextBlock( t, st, &types.BeaconBlockBody{ @@ -1093,15 +1003,9 @@ func TestTransitionValidatorCap_DoubleEviction(t *testing.T) { st.EVMInflationWithdrawal(), { Index: 0, - Validator: smallVal1Idx, - Address: smallest1ValAddr, - Amount: smallest1Val.Amount, - }, - { - Index: 1, - Validator: smallVal2Idx, - Address: smallestVal2Addr, - Amount: smallestVal2.Amount, + Validator: smallestValIdx, + Address: valToEvictAddr, + Amount: valToEvict.Amount, }, }, BaseFeePerGas: math.NewU256(0), @@ -1111,35 +1015,9 @@ func TestTransitionValidatorCap_DoubleEviction(t *testing.T) { }, ) - vals, err = sp.Transition(ctx, st, blk) + // run the test + _, err = sp.Transition(ctx, st, blk) require.NoError(t, err) - require.LessOrEqual(t, uint64(len(vals)), cs.ValidatorSetCap()) - require.Len(t, vals, 4) // just replaced two validators - - // turn vals into map to avoid ordering issues - valsSet := make(map[string]*transition.ValidatorUpdate) - for _, v := range vals { - valsSet[v.Pubkey.String()] = v - } - require.Equal(t, len(vals), len(valsSet)) // no duplicates - - // check that we added the incoming validator at the epoch turn - addedVal1, found := valsSet[extraVal1.Pubkey.String()] - require.True(t, found) - require.Equal(t, extraVal1.EffectiveBalance, addedVal1.EffectiveBalance) - - addedVal2, found := valsSet[extraVal2.Pubkey.String()] - require.True(t, found) - require.Equal(t, extraVal2.EffectiveBalance, addedVal2.EffectiveBalance) - - // check that we removed the smallest validators at the epoch turn - removedVal1, found := valsSet[smallVal1.Pubkey.String()] - require.True(t, found) - require.Equal(t, math.Gwei(0), removedVal1.EffectiveBalance) - - removeldVal2, found := valsSet[smallVal2.Pubkey.String()] - require.True(t, found) - require.Equal(t, math.Gwei(0), removeldVal2.EffectiveBalance) } func generateTestExecutionAddress( From ee2611b1d8c3999d3b66b1126621a4d54b383308 Mon Sep 17 00:00:00 2001 From: aBear Date: Sat, 7 Dec 2024 23:41:29 -0500 Subject: [PATCH 09/16] typos --- state-transition/core/state_processor_validators.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index 3d8c895ce..e039e0ac2 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -79,9 +79,9 @@ func (sp *StateProcessor[ } // Enforce the validator set cap by: - // 1- retrieve validators active next epoch - // 2- sort them by stake - // 3- drop enough validators to fulfill the cap + // 1- retrieving validators active next epoch + // 2- sorting them by stake + // 3- dropping enough validators to fulfill the cap nextEpochVals, err := sp.nextEpochValidatorSet(st) if err != nil { return fmt.Errorf("registry update, failed retrieving next epoch vals: %w", err) @@ -112,7 +112,7 @@ func (sp *StateProcessor[ } }) - // We do not currently have a cap on validator churn, so we stop + // We do not currently have a cap on validators churn, so we stop // validators this epoch and we withdraw them next epoch for li := range uint64(len(nextEpochVals)) - sp.cs.ValidatorSetCap() { valToEject := nextEpochVals[li] From 7f8df4408b5136bd528b4589a503a6f08ac1db99 Mon Sep 17 00:00:00 2001 From: aBear Date: Sun, 8 Dec 2024 00:02:52 -0500 Subject: [PATCH 10/16] wip: fixing returned diffs --- state-transition/core/state_processor.go | 48 +++++----- .../core/state_processor_genesis.go | 11 ++- .../core/state_processor_staking_test.go | 67 ++++++++++---- .../core/state_processor_validators.go | 87 ++++++------------- state-transition/core/types.go | 1 + 5 files changed, 116 insertions(+), 98 deletions(-) diff --git a/state-transition/core/state_processor.go b/state-transition/core/state_processor.go index 36bde7a4d..c396f8225 100644 --- a/state-transition/core/state_processor.go +++ b/state-transition/core/state_processor.go @@ -23,7 +23,6 @@ package core import ( "bytes" "fmt" - "sync" "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/consensus-types/types" @@ -99,19 +98,6 @@ type StateProcessor[ ds DepositStore[DepositT] // metrics is the metrics for the service. metrics *stateProcessorMetrics - - // valSetMu protects valSetByEpoch from concurrent accesses - valSetMu sync.RWMutex - - // valSetByEpoch tracks the set of validators active at the latest epochs. - // This is useful to optimize validators set updates. - // Note: Transition may be called multiple times on different, - // non/finalized blocks, so at some point valSetByEpoch may contain - // informations from blocks not finalized. This should be fine as long - // as a block is finalized eventually, and its changes will be the last - // ones. - // We prune the map to preserve only current and previous epoch - valSetByEpoch map[math.Epoch][]ValidatorT } // NewStateProcessor creates a new state processor. @@ -187,7 +173,6 @@ func NewStateProcessor[ fGetAddressFromPubKey: fGetAddressFromPubKey, ds: ds, metrics: newStateProcessorMetrics(telemetrySink), - valSetByEpoch: make(map[math.Epoch][]ValidatorT, 0), } } @@ -374,22 +359,43 @@ func (sp *StateProcessor[ ]) processEpoch( st BeaconStateT, ) (transition.ValidatorUpdates, error) { - if err := sp.processRewardsAndPenalties(st); err != nil { + slot, err := st.GetSlot() + if err != nil { return nil, err } - if err := sp.processRegistryUpdates(st); err != nil { + + currentEpoch := sp.cs.SlotToEpoch(slot) + currentActiveVals, err := sp.getActiveVals(st, currentEpoch) + if err != nil { return nil, err } - if err := sp.processEffectiveBalanceUpdates(st); err != nil { + + if err = sp.processRewardsAndPenalties(st); err != nil { + return nil, err + } + if err = sp.processRegistryUpdates(st); err != nil { return nil, err } - if err := sp.processSlashingsReset(st); err != nil { + if err = sp.processEffectiveBalanceUpdates(st); err != nil { return nil, err } - if err := sp.processRandaoMixesReset(st); err != nil { + if err = sp.processSlashingsReset(st); err != nil { return nil, err } - return sp.processValidatorsSetUpdates(st) + if err = sp.processRandaoMixesReset(st); err != nil { + return nil, err + } + if err = sp.processValidatorSetCap(st); err != nil { + return nil, err + } + + nextEpoch := currentEpoch + 1 + nextActiveVals, err := sp.getActiveVals(st, nextEpoch) + if err != nil { + return nil, err + } + + return sp.validatorSetsDiffs(currentActiveVals, nextActiveVals), nil } // processBlockHeader processes the header and ensures it matches the local diff --git a/state-transition/core/state_processor_genesis.go b/state-transition/core/state_processor_genesis.go index 0cd7bb9c8..d6d51fc5e 100644 --- a/state-transition/core/state_processor_genesis.go +++ b/state-transition/core/state_processor_genesis.go @@ -150,9 +150,14 @@ func (sp *StateProcessor[ return nil, err } - return sp.processValidatorsSetUpdates(st) + activeVals, err := sp.getActiveVals(st, 0) + if err != nil { + return nil, err + } + return sp.validatorSetsDiffs(nil, activeVals), nil } +//nolint:lll // let it be. func (sp *StateProcessor[ _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, ]) processGenesisActivation( @@ -170,9 +175,13 @@ func (sp *StateProcessor[ if err != nil { return fmt.Errorf("genesis activation, failed listing validators: %w", err) } + minEffectiveBalance := math.Gwei(sp.cs.EjectionBalance() + sp.cs.EffectiveBalanceIncrement()) var idx math.ValidatorIndex for _, val := range vals { + if val.GetEffectiveBalance() < minEffectiveBalance { + continue + } val.SetActivationEligibilityEpoch(0) val.SetActivationEpoch(0) idx, err = st.ValidatorIndexByPubkey(val.GetPubkey()) diff --git a/state-transition/core/state_processor_staking_test.go b/state-transition/core/state_processor_staking_test.go index ffe2d7f0b..eb7069fb6 100644 --- a/state-transition/core/state_processor_staking_test.go +++ b/state-transition/core/state_processor_staking_test.go @@ -78,14 +78,14 @@ func TestTransitionUpdateValidators(t *testing.T) { genPayloadHeader = new(types.ExecutionPayloadHeader).Empty() genVersion = version.FromUint32[common.Version](version.Deneb) ) - genVals, err := sp.InitializePreminedBeaconStateFromEth1( + valDiff, err := sp.InitializePreminedBeaconStateFromEth1( st, genDeposits, genPayloadHeader, genVersion, ) require.NoError(t, err) - require.Len(t, genVals, len(genDeposits)) + require.Len(t, valDiff, len(genDeposits)) // STEP 1: top up a genesis validator balance blkDeposit := &types.Deposit{ @@ -117,7 +117,7 @@ func TestTransitionUpdateValidators(t *testing.T) { require.NoError(t, ds.EnqueueDeposits(blk1.Body.Deposits)) // run the test - valDiff, err := sp.Transition(ctx, st, blk1) + valDiff, err = sp.Transition(ctx, st, blk1) require.NoError(t, err) require.Empty(t, valDiff) // validators set updates only at epoch turn @@ -167,8 +167,14 @@ func TestTransitionUpdateValidators(t *testing.T) { valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) require.Len(t, valDiff, 1) // just topped up one validator - - expectedBalance = genDeposits[2].Amount + blkDeposit.Amount + require.Equal( + t, + &transition.ValidatorUpdate{ + Pubkey: blkDeposit.Pubkey, + EffectiveBalance: expectedBalance, + }, + valDiff[0], + ) expectedEffectiveBalance = expectedBalance balance, err = st.GetBalance(idx) @@ -252,9 +258,9 @@ func TestTransitionCreateValidator(t *testing.T) { require.NoError(t, ds.EnqueueDeposits(blk1.Body.Deposits)) // run the test - updatedVals, err := sp.Transition(ctx, st, blk1) + valDiff, err := sp.Transition(ctx, st, blk1) require.NoError(t, err) - require.Empty(t, updatedVals) // validators set updates only at epoch turn + require.Empty(t, valDiff) // validators set updates only at epoch turn // check validator balances are duly updated var ( @@ -300,7 +306,7 @@ func TestTransitionCreateValidator(t *testing.T) { }, ) - valDiff, err := sp.Transition(ctx, st, blk) + valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) require.Empty(t, valDiff) // new validator is only eligible for activation @@ -337,8 +343,17 @@ func TestTransitionCreateValidator(t *testing.T) { ) // run the test - _, err = sp.Transition(ctx, st, blk) + valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) + require.Len(t, valDiff, 1) + require.Equal( + t, + &transition.ValidatorUpdate{ + Pubkey: blkDeposit.Pubkey, + EffectiveBalance: expectedBalance, + }, + valDiff[0], + ) extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) @@ -672,8 +687,9 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { require.NoError(t, ds.EnqueueDeposits(blk1.Body.Deposits)) // run the test - _, err = sp.Transition(ctx, st, blk1) + valDiff, err := sp.Transition(ctx, st, blk1) require.NoError(t, err) + require.Empty(t, valDiff) extraValIdx, err := st.ValidatorIndexByPubkey(extraValDeposit.Pubkey) require.NoError(t, err) @@ -708,8 +724,9 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { ) // run the test - _, err = sp.Transition(ctx, st, blk) + valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) + require.Empty(t, valDiff) // check extra validator is added with Withdraw epoch duly set extraVal, err = st.ValidatorByIndex(extraValIdx) @@ -743,8 +760,9 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { ) // run the test - _, err = sp.Transition(ctx, st, blk) + valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) + require.Empty(t, valDiff) extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) @@ -878,8 +896,9 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { require.NoError(t, ds.EnqueueDeposits(blk1.Body.Deposits)) // run the test - _, err = sp.Transition(ctx, st, blk1) + valDiff, err := sp.Transition(ctx, st, blk1) require.NoError(t, err) + require.Empty(t, valDiff) extraValIdx, err := st.ValidatorIndexByPubkey(extraValDeposit.Pubkey) require.NoError(t, err) @@ -923,8 +942,9 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { ) // run the test - _, err = sp.Transition(ctx, st, blk) + valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) + require.Empty(t, valDiff) // check extra validator is added with Withdraw epoch duly set extraVal, err = st.ValidatorByIndex(extraValIdx) @@ -965,8 +985,25 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { ) // run the test - _, err = sp.Transition(ctx, st, blk) + valDiff, err = sp.Transition(ctx, st, blk) require.NoError(t, err) + require.Len(t, valDiff, 2) + require.Equal( + t, + &transition.ValidatorUpdate{ + Pubkey: extraVal.Pubkey, + EffectiveBalance: extraVal.EffectiveBalance, + }, + valDiff[0], + ) + require.Equal( + t, + &transition.ValidatorUpdate{ + Pubkey: smallestVal.Pubkey, + EffectiveBalance: 0, + }, + valDiff[1], + ) extraVal, err = st.ValidatorByIndex(extraValIdx) require.NoError(t, err) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index e039e0ac2..dc62134b5 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -31,7 +31,7 @@ import ( "github.com/sourcegraph/conc/iter" ) -//nolint:lll, gocognit // TODO fix +//nolint:lll // let it be func (sp *StateProcessor[ _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, ]) processRegistryUpdates( @@ -78,11 +78,30 @@ func (sp *StateProcessor[ } } + // validators registry will be possibly further modified in order to enforce + // validators set cap. We will do that at the end of processEpoch, once all + // Eth 2.0 like transitions has been done (notable EffectiveBalances handling). + return nil +} + +//nolint:lll // let it be +func (sp *StateProcessor[ + _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, +]) processValidatorSetCap( + st BeaconStateT, +) error { // Enforce the validator set cap by: // 1- retrieving validators active next epoch // 2- sorting them by stake // 3- dropping enough validators to fulfill the cap - nextEpochVals, err := sp.nextEpochValidatorSet(st) + + slot, err := st.GetSlot() + if err != nil { + return err + } + nextEpoch := sp.cs.SlotToEpoch(slot) + 1 + + nextEpochVals, err := sp.getActiveVals(st, nextEpoch) if err != nil { return fmt.Errorf("registry update, failed retrieving next epoch vals: %w", err) } @@ -114,6 +133,7 @@ func (sp *StateProcessor[ // We do not currently have a cap on validators churn, so we stop // validators this epoch and we withdraw them next epoch + var idx math.ValidatorIndex for li := range uint64(len(nextEpochVals)) - sp.cs.ValidatorSetCap() { valToEject := nextEpochVals[li] valToEject.SetExitEpoch(nextEpoch) @@ -126,50 +146,8 @@ func (sp *StateProcessor[ return fmt.Errorf("registry update, failed ejecting validator idx %d: %w", li, err) } } - return nil -} - -// processValidatorsSetUpdates returns the validators set updates that -// will be used by consensus. -func (sp *StateProcessor[ - _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, -]) processValidatorsSetUpdates( - st BeaconStateT, -) (transition.ValidatorUpdates, error) { - // at this state slot has not been updated yet so - // we pick nextEpochValidatorSet - activeVals, err := sp.nextEpochValidatorSet(st) - if err != nil { - return nil, err - } - // pick prev epoch validators - slot, err := st.GetSlot() - if err != nil { - return nil, err - } - - sp.valSetMu.Lock() - defer sp.valSetMu.Unlock() - - // prevEpoch is calculated assuming current block - // will turn epoch but we have not update slot yet - prevEpoch := sp.cs.SlotToEpoch(slot) - currEpoch := prevEpoch + 1 - if slot == 0 { - currEpoch = 0 // prevEpoch for genesis is zero - } - prevEpochVals := sp.valSetByEpoch[prevEpoch] // picks nil if it's genesis - - // calculate diff - res := sp.validatorSetsDiffs(prevEpochVals, activeVals) - - // clear up sets we won't lookup to anymore - sp.valSetByEpoch[currEpoch] = activeVals - if prevEpoch >= 1 { - delete(sp.valSetByEpoch, prevEpoch-1) - } - return res, nil + return nil } // Note: validatorSetsDiffs does not need to be a StateProcessor method @@ -231,29 +209,16 @@ func (*StateProcessor[ // validator set would be. func (sp *StateProcessor[ _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, -]) nextEpochValidatorSet(st BeaconStateT) ([]ValidatorT, error) { - slot, err := st.GetSlot() - if err != nil { - return nil, err - } - nextEpoch := sp.cs.SlotToEpoch(slot) + 1 - +]) getActiveVals(st BeaconStateT, epoch math.Epoch) ([]ValidatorT, error) { vals, err := st.GetValidators() if err != nil { return nil, err } activeVals := make([]ValidatorT, 0, len(vals)) for _, val := range vals { - if val.GetEffectiveBalance() <= math.U64(sp.cs.EjectionBalance()) { - continue - } - if val.GetActivationEligibilityEpoch() == nextEpoch { - continue - } - if val.GetWithdrawableEpoch() == nextEpoch { - continue + if val.IsActive(epoch) { + activeVals = append(activeVals, val) } - activeVals = append(activeVals, val) } return activeVals, nil diff --git a/state-transition/core/types.go b/state-transition/core/types.go index d6cc6e9c0..fcf29cc61 100644 --- a/state-transition/core/types.go +++ b/state-transition/core/types.go @@ -245,6 +245,7 @@ type Validator[ IsEligibleForActivationQueue(threshold math.Gwei) bool IsEligibleForActivation(finalizedEpoch math.Epoch) bool + IsActive(epoch math.Epoch) bool // GetPubkey returns the public key of the validator. GetPubkey() crypto.BLSPubkey From cda83f66f9d1b2c11e8e15a113d3da98dfcf91a2 Mon Sep 17 00:00:00 2001 From: aBear Date: Sun, 8 Dec 2024 10:27:29 -0500 Subject: [PATCH 11/16] nit --- state-transition/core/state_processor.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/state-transition/core/state_processor.go b/state-transition/core/state_processor.go index c396f8225..7a603c87a 100644 --- a/state-transition/core/state_processor.go +++ b/state-transition/core/state_processor.go @@ -364,6 +364,8 @@ func (sp *StateProcessor[ return nil, err } + // track validators set before updating it, to be able to + // inform consensus of the validators set changes currentEpoch := sp.cs.SlotToEpoch(slot) currentActiveVals, err := sp.getActiveVals(st, currentEpoch) if err != nil { @@ -385,10 +387,14 @@ func (sp *StateProcessor[ if err = sp.processRandaoMixesReset(st); err != nil { return nil, err } + + // only after we have fully updated validators, we enforce + // a cap on the validators set if err = sp.processValidatorSetCap(st); err != nil { return nil, err } + // finally compute diffs in validator set to duly update consensus nextEpoch := currentEpoch + 1 nextActiveVals, err := sp.getActiveVals(st, nextEpoch) if err != nil { From 9f30dd84969eb4a7e27b84e74ebe1b924c000ed9 Mon Sep 17 00:00:00 2001 From: aBear Date: Sun, 8 Dec 2024 11:39:55 -0500 Subject: [PATCH 12/16] fixed Bartio backward compatibility --- .../core/state_processor_validators.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index dc62134b5..af0ceb21d 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -25,6 +25,7 @@ import ( "fmt" "slices" + "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/primitives/bytes" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/transition" @@ -214,10 +215,21 @@ func (sp *StateProcessor[ if err != nil { return nil, err } + activeVals := make([]ValidatorT, 0, len(vals)) - for _, val := range vals { - if val.IsActive(epoch) { - activeVals = append(activeVals, val) + if sp.cs.DepositEth1ChainID() == spec.BartioChainID { + // Bartio does not properly handle validators epochs, so + // we have an ad-hoc definition of active validator there + for _, val := range vals { + if val.GetEffectiveBalance() > math.U64(sp.cs.EjectionBalance()) { + activeVals = append(activeVals, val) + } + } + } else { + for _, val := range vals { + if val.IsActive(epoch) { + activeVals = append(activeVals, val) + } } } From a720a158f2378858a2ca60ff240f839a1d32790a Mon Sep 17 00:00:00 2001 From: aBear Date: Sun, 8 Dec 2024 14:18:33 -0500 Subject: [PATCH 13/16] wip: fixing backward compatibility --- .../core/state_processor_validators.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index af0ceb21d..03fc427fb 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -216,8 +216,14 @@ func (sp *StateProcessor[ return nil, err } + slot, err := st.GetSlot() + if err != nil { + return nil, err + } + activeVals := make([]ValidatorT, 0, len(vals)) - if sp.cs.DepositEth1ChainID() == spec.BartioChainID { + switch { + case sp.cs.DepositEth1ChainID() == spec.BartioChainID: // Bartio does not properly handle validators epochs, so // we have an ad-hoc definition of active validator there for _, val := range vals { @@ -225,13 +231,20 @@ func (sp *StateProcessor[ activeVals = append(activeVals, val) } } - } else { + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot < math.U64(spec.BoonetFork3Height): + // Boonet inherits Bartio processing till for 3 + for _, val := range vals { + if val.GetEffectiveBalance() > math.U64(sp.cs.EjectionBalance()) { + activeVals = append(activeVals, val) + } + } + default: for _, val := range vals { if val.IsActive(epoch) { activeVals = append(activeVals, val) } } } - return activeVals, nil } From 2772911da209e698733f64019b94776be062e0fd Mon Sep 17 00:00:00 2001 From: aBear Date: Sun, 8 Dec 2024 14:41:56 -0500 Subject: [PATCH 14/16] wip: fixing backward compatibility --- .../core/state_processor_validators.go | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index 03fc427fb..9125f02df 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -38,15 +38,28 @@ func (sp *StateProcessor[ ]) processRegistryUpdates( st BeaconStateT, ) error { - vals, err := st.GetValidators() + slot, err := st.GetSlot() if err != nil { - return fmt.Errorf("registry update, failed listing validators: %w", err) + return fmt.Errorf("registry update, failed loading slot: %w", err) } - slot, err := st.GetSlot() + switch { + case sp.cs.DepositEth1ChainID() == spec.BartioChainID: + // Bartio does not properly handle validators registry + return nil + case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && + slot < math.U64(spec.BoonetFork3Height): + // Boonet inherits Bartio processing till fork 3 + return nil + default: + // processing below + } + + vals, err := st.GetValidators() if err != nil { - return fmt.Errorf("registry update, failed loading slot: %w", err) + return fmt.Errorf("registry update, failed listing validators: %w", err) } + currEpoch := sp.cs.SlotToEpoch(slot) nextEpoch := currEpoch + 1 @@ -233,7 +246,7 @@ func (sp *StateProcessor[ } case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && slot < math.U64(spec.BoonetFork3Height): - // Boonet inherits Bartio processing till for 3 + // Boonet inherits Bartio processing till fork 3 for _, val := range vals { if val.GetEffectiveBalance() > math.U64(sp.cs.EjectionBalance()) { activeVals = append(activeVals, val) From c69dea0e398639b12a2c5c521571db0e60d922a4 Mon Sep 17 00:00:00 2001 From: aBear Date: Mon, 9 Dec 2024 11:12:54 -0500 Subject: [PATCH 15/16] nits --- state-transition/core/state_processor_rewards_penalties.go | 3 ++- state-transition/core/state_processor_validators.go | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/state-transition/core/state_processor_rewards_penalties.go b/state-transition/core/state_processor_rewards_penalties.go index cf9b96255..65cc9dee4 100644 --- a/state-transition/core/state_processor_rewards_penalties.go +++ b/state-transition/core/state_processor_rewards_penalties.go @@ -42,9 +42,10 @@ func (sp *StateProcessor[ switch { case sp.cs.DepositEth1ChainID() == spec.BartioChainID: - // go head doing the processing, eve + // go head doing the processing case sp.cs.DepositEth1ChainID() == spec.BoonetEth1ChainID && slot < math.U64(spec.BoonetFork3Height): + // go head doing the processing default: // no real need to perform hollowProcessRewardsAndPenalties return nil diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index 9125f02df..e5b2b4d7b 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -146,7 +146,7 @@ func (sp *StateProcessor[ }) // We do not currently have a cap on validators churn, so we stop - // validators this epoch and we withdraw them next epoch + // validators next epoch and we withdraw them the epoch after var idx math.ValidatorIndex for li := range uint64(len(nextEpochVals)) - sp.cs.ValidatorSetCap() { valToEject := nextEpochVals[li] @@ -154,10 +154,10 @@ func (sp *StateProcessor[ valToEject.SetWithdrawableEpoch(nextEpoch + 1) idx, err = st.ValidatorIndexByPubkey(valToEject.GetPubkey()) if err != nil { - return fmt.Errorf("registry update, failed loading validator index: %w", err) + return fmt.Errorf("validators cap, failed loading validator index: %w", err) } if err = st.UpdateValidatorAtIndex(idx, valToEject); err != nil { - return fmt.Errorf("registry update, failed ejecting validator idx %d: %w", li, err) + return fmt.Errorf("validator cap, failed ejecting validator idx %d: %w", li, err) } } From 69c753edebb8f99a3b331d817a72c6c54a29d233 Mon Sep 17 00:00:00 2001 From: aBear Date: Mon, 9 Dec 2024 13:06:11 -0500 Subject: [PATCH 16/16] nit --- state-transition/core/state_processor_validators.go | 1 + 1 file changed, 1 insertion(+) diff --git a/state-transition/core/state_processor_validators.go b/state-transition/core/state_processor_validators.go index e5b2b4d7b..e00078fce 100644 --- a/state-transition/core/state_processor_validators.go +++ b/state-transition/core/state_processor_validators.go @@ -166,6 +166,7 @@ func (sp *StateProcessor[ // Note: validatorSetsDiffs does not need to be a StateProcessor method // but it helps simplifying generic instantiation. +// TODO: Turn this into a free function func (*StateProcessor[ _, _, _, _, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, ]) validatorSetsDiffs(