diff --git a/mod/state-transition/pkg/core/state_processor.go b/mod/state-transition/pkg/core/state_processor.go index ccf143a969..bcaa2f0198 100644 --- a/mod/state-transition/pkg/core/state_processor.go +++ b/mod/state-transition/pkg/core/state_processor.go @@ -94,6 +94,11 @@ type StateProcessor[ // processingGenesis allows initializing correctly // eth1 deposit index upon genesis processingGenesis bool + + // prevEpochValidators tracks the set of validators active during + // previous epoch. This is useful at the turn of the epoch to send to + // consensus only the diffs rather than the full updated validator set. + prevEpochValidators []*transition.ValidatorUpdate } // NewStateProcessor creates a new state processor. @@ -163,6 +168,7 @@ func NewStateProcessor[ executionEngine: executionEngine, signer: signer, fGetAddressFromPubKey: fGetAddressFromPubKey, + prevEpochValidators: make([]*transition.ValidatorUpdate, 0), } } diff --git a/mod/state-transition/pkg/core/state_processor_staking_test.go b/mod/state-transition/pkg/core/state_processor_staking_test.go index 6cc2c055dd..d361cfd154 100644 --- a/mod/state-transition/pkg/core/state_processor_staking_test.go +++ b/mod/state-transition/pkg/core/state_processor_staking_test.go @@ -21,7 +21,6 @@ package core_test import ( - "fmt" "testing" "github.com/berachain/beacon-kit/mod/config/pkg/spec" @@ -214,15 +213,7 @@ func TestTransitionUpdateValidator(t *testing.T) { newEpochVals, err := sp.Transition(ctx, beaconState, blk) require.NoError(t, err) - require.Len(t, newEpochVals, len(genDeposits)) // just topped up one validator - - // Assuming genesis order is preserved here which is not necessary - // TODO: remove this assumption - - // all genesis validators other than the last are unchanged - for i := range len(genDeposits) - 1 { - require.Equal(t, genVals[i], newEpochVals[i], fmt.Sprintf("idx: %d", i)) - } + require.Len(t, newEpochVals, 1) // just topped up one validator expectedBalance = genDeposits[2].Amount + blkDeposit.Amount expectedEffectiveBalance = expectedBalance @@ -400,15 +391,7 @@ func TestTransitionCreateValidator(t *testing.T) { newEpochVals, err := sp.Transition(ctx, beaconState, blk) require.NoError(t, err) - require.Len(t, newEpochVals, len(genDeposits)+1) - - // Assuming genesis order is preserved here which is not necessary - // TODO: remove this assumption - - // all genesis validators are unchanged - for i := range len(genDeposits) { - require.Equal(t, genVals[i], newEpochVals[i], fmt.Sprintf("idx: %d", i)) - } + require.Len(t, newEpochVals, 1) // just added 1 validator expectedBalance = blkDeposit.Amount expectedEffectiveBalance = expectedBalance diff --git a/mod/state-transition/pkg/core/state_processor_validators.go b/mod/state-transition/pkg/core/state_processor_validators.go index f9fb35b6f6..d252c0acf8 100644 --- a/mod/state-transition/pkg/core/state_processor_validators.go +++ b/mod/state-transition/pkg/core/state_processor_validators.go @@ -21,13 +21,14 @@ package core import ( + "github.com/berachain/beacon-kit/mod/primitives/pkg/bytes" "github.com/berachain/beacon-kit/mod/primitives/pkg/math" "github.com/berachain/beacon-kit/mod/primitives/pkg/transition" "github.com/sourcegraph/conc/iter" ) // processValidatorsSetUpdates returns the validators set updates that -// will be used by consensus +// will be used by consensus. func (sp *StateProcessor[ _, _, _, BeaconStateT, _, _, _, _, _, _, _, _, ValidatorT, _, _, _, _, ]) processValidatorsSetUpdates( @@ -46,10 +47,10 @@ func (sp *StateProcessor[ } } - // TODO: a more efficient handling would be to only send back to consensus - // updated validators (including evicted ones), rather than the full list - - return iter.MapErr( + // We need to inform consensus of the changes incurred by the validator set + // We strive to send only diffs (added,updated or removed validators) and + // avoid re-sending validators that have not changed. + currentValSet, err := iter.MapErr( activeVals, func(val *ValidatorT) (*transition.ValidatorUpdate, error) { v := (*val) @@ -59,4 +60,45 @@ func (sp *StateProcessor[ }, nil }, ) + if err != nil { + return nil, err + } + + res := make([]*transition.ValidatorUpdate, 0) + + prevValsSet := make(map[string]math.Gwei, len(sp.prevEpochValidators)) + for _, v := range sp.prevEpochValidators { + prevValsSet[string(v.Pubkey[:])] = v.EffectiveBalance + } + + for _, newVal := range currentValSet { + key := string(newVal.Pubkey[:]) + oldBal, found := prevValsSet[key] + if !found { + // new validator, we add it with its weight + res = append(res, newVal) + continue + } + if oldBal != newVal.EffectiveBalance { + // validator updated, we add it with new weight + res = append(res, newVal) + } + + // consume pre-existing validators + delete(prevValsSet, key) + } + + // prevValsSet now contains all evicted validators (and only those) + for pkBytes := range prevValsSet { + //#nosec:G703 // bytes comes from a pk + pk, _ := bytes.ToBytes48([]byte(pkBytes)) + res = append(res, &transition.ValidatorUpdate{ + Pubkey: pk, + EffectiveBalance: 0, // signal val eviction to consensus + }) + } + + // rotate validators set to new epoch ones + sp.prevEpochValidators = currentValSet + return res, nil }