Skip to content

Commit

Permalink
feat(state-transition): make validators epochs handling close to Eth2…
Browse files Browse the repository at this point in the history
….0 specs (#2226)
  • Loading branch information
abi87 authored Dec 9, 2024
1 parent ccfdcde commit b00e245
Show file tree
Hide file tree
Showing 10 changed files with 692 additions and 714 deletions.
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
94 changes: 27 additions & 67 deletions state-transition/core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ package core
import (
"bytes"
"fmt"
"sync"

"github.com/berachain/beacon-kit/config/spec"
"github.com/berachain/beacon-kit/consensus-types/types"
"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"
Expand Down Expand Up @@ -100,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.
Expand Down Expand Up @@ -188,7 +173,6 @@ func NewStateProcessor[
fGetAddressFromPubKey: fGetAddressFromPubKey,
ds: ds,
metrics: newStateProcessorMetrics(telemetrySink),
valSetByEpoch: make(map[math.Epoch][]ValidatorT, 0),
}
}

Expand Down Expand Up @@ -380,24 +364,20 @@ func (sp *StateProcessor[
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
// 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 {
return nil, err
}

if err = sp.processRewardsAndPenalties(st); err != nil {
return nil, err
}
if err = sp.processRegistryUpdates(st); err != nil {
return nil, err
}
if err = sp.processEffectiveBalanceUpdates(st); err != nil {
return nil, err
}
Expand All @@ -407,7 +387,21 @@ func (sp *StateProcessor[
if err = sp.processRandaoMixesReset(st); err != nil {
return nil, err
}
return sp.processValidatorsSetUpdates(st)

// 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 {
return nil, err
}

return sp.validatorSetsDiffs(currentActiveVals, nextActiveVals), nil
}

// processBlockHeader processes the header and ensures it matches the local
Expand Down Expand Up @@ -492,40 +486,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
//
Expand Down
54 changes: 52 additions & 2 deletions 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 // todo fix.
func (sp *StateProcessor[
_, BeaconBlockBodyT, BeaconBlockHeaderT, BeaconStateT, _, DepositT,
Eth1DataT, _, ExecutionPayloadHeaderT, ForkT, _, _, ValidatorT, _, _, _, _,
Expand Down Expand Up @@ -103,6 +105,11 @@ func (sp *StateProcessor[
}
}

// process activations
if err := sp.processGenesisActivation(st); 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 Expand Up @@ -143,5 +150,48 @@ 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(
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)
}
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())
if err != nil {
return err
}
if err = st.UpdateValidatorAtIndex(idx, val); err != nil {
return err
}
}
return nil
}
}
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
Loading

0 comments on commit b00e245

Please sign in to comment.