Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(state-transition): make validators epochs handling close to Eth2.0 specs #2226

Merged
merged 16 commits into from
Dec 9, 2024
46 changes: 30 additions & 16 deletions consensus-types/types/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,24 +246,18 @@ 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)
}

// 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
Expand Down Expand Up @@ -296,9 +290,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
Expand All @@ -325,12 +317,34 @@ func (v *Validator) SetEffectiveBalance(balance math.Gwei) {
v.EffectiveBalance = balance
}

// SetWithdrawableEpoch sets the epoch when the validator can withdraw.
func (v *Validator) SetActivationEligibilityEpoch(e math.Epoch) {
v.ActivationEligibilityEpoch = e
}

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
}

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
}

// GetWithdrawableEpoch returns the epoch when the validator can withdraw.
func (v Validator) GetWithdrawableEpoch() math.Epoch {
return v.WithdrawableEpoch
}
Expand Down
3 changes: 3 additions & 0 deletions state-transition/core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
30 changes: 29 additions & 1 deletion state-transition/core/state_processor_genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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, _, _, _, _,
Expand Down Expand Up @@ -103,6 +105,32 @@ 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
}
}
}

// Handle special case bartio genesis.
validatorsRoot := common.Root(hex.MustToBytes(spec.BartioValRoot))
if sp.cs.DepositEth1ChainID() != spec.BartioChainID {
Expand Down
42 changes: 28 additions & 14 deletions state-transition/core/state_processor_genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,19 @@ 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())
require.Equal(t, math.Epoch(0), val.GetActivationEpoch())

valBal, err := bs.GetBalance(idx)
require.NoError(t, err)
Expand Down Expand Up @@ -268,15 +275,28 @@ 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)

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)
require.Equal(t, val.EffectiveBalance, valBal)
Expand All @@ -291,16 +311,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 (
Expand Down
126 changes: 4 additions & 122 deletions state-transition/core/state_processor_staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -192,130 +189,15 @@ 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(),
abi87 marked this conversation as resolved.
Show resolved Hide resolved
math.Gwei(sp.cs.EffectiveBalanceIncrement()),
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) {
Copy link
Collaborator Author

@abi87 abi87 Dec 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to state-transition/core/state_processor_validators.go, as it should be

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) (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed really

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
Expand All @@ -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(),
)
abi87 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading
Loading