From 7f8df4408b5136bd528b4589a503a6f08ac1db99 Mon Sep 17 00:00:00 2001 From: aBear Date: Sun, 8 Dec 2024 00:02:52 -0500 Subject: [PATCH] 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