package altair import ( "context" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/state" "github.com/prysmaticlabs/prysm/shared/params" "go.opencensus.io/trace" ) // InitializeEpochValidators gets called at the beginning of process epoch cycle to return // pre computed instances of validators attesting records and total // balances attested in an epoch. func InitializeEpochValidators(ctx context.Context, st state.BeaconStateAltair) ([]*precompute.Validator, *precompute.Balance, error) { ctx, span := trace.StartSpan(ctx, "altair.InitializeEpochValidators") defer span.End() pValidators := make([]*precompute.Validator, st.NumValidators()) bal := &precompute.Balance{} prevEpoch := helpers.PrevEpoch(st) currentEpoch := helpers.CurrentEpoch(st) inactivityScores, err := st.InactivityScores() if err != nil { return nil, nil, err } // This shouldn't happen with a correct beacon state, // but rather be safe to defend against index out of bound panics. if st.NumValidators() > len(inactivityScores) { return nil, nil, errors.New("num of validators can't be greater than length of inactivity scores") } if err := st.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error { // Was validator withdrawable or slashed withdrawable := currentEpoch >= val.WithdrawableEpoch() pVal := &precompute.Validator{ IsSlashed: val.Slashed(), IsWithdrawableCurrentEpoch: withdrawable, CurrentEpochEffectiveBalance: val.EffectiveBalance(), InactivityScore: inactivityScores[idx], } // Validator active current epoch if helpers.IsActiveValidatorUsingTrie(val, currentEpoch) { pVal.IsActiveCurrentEpoch = true bal.ActiveCurrentEpoch += val.EffectiveBalance() } // Validator active previous epoch if helpers.IsActiveValidatorUsingTrie(val, prevEpoch) { pVal.IsActivePrevEpoch = true bal.ActivePrevEpoch += val.EffectiveBalance() } pValidators[idx] = pVal return nil }); err != nil { return nil, nil, errors.Wrap(err, "could not initialize epoch validator") } return pValidators, bal, nil } // ProcessInactivityScores of beacon chain. This updates inactivity scores of beacon chain and // updates the precompute validator struct for later processing. The inactivity scores work as following: // For fully inactive validators and perfect active validators, the effect is the same as before Altair. // For a validator is inactive and the chain fails to finalize, the inactivity score increases by a fixed number, the total loss after N epochs is proportional to N**2/2. // For imperfectly active validators. The inactivity score's behavior is specified by this function: // If a validator fails to submit an attestation with the correct target, their inactivity score goes up by 4. // If they successfully submit an attestation with the correct source and target, their inactivity score drops by 1 // If the chain has recently finalized, each validator's score drops by 16. func ProcessInactivityScores( ctx context.Context, state state.BeaconState, vals []*precompute.Validator, ) (state.BeaconState, []*precompute.Validator, error) { cfg := params.BeaconConfig() if helpers.CurrentEpoch(state) == cfg.GenesisEpoch { return state, vals, nil } inactivityScores, err := state.InactivityScores() if err != nil { return nil, nil, err } bias := cfg.InactivityScoreBias recoveryRate := cfg.InactivityScoreRecoveryRate for i, v := range vals { if !precompute.EligibleForRewards(v) { continue } if v.IsPrevEpochTargetAttester && !v.IsSlashed { // Decrease inactivity score when validator gets target correct. if v.InactivityScore > 0 { score := uint64(1) // Prevents underflow below 0. if score > v.InactivityScore { score = v.InactivityScore } v.InactivityScore -= score } } else { v.InactivityScore += bias } if !helpers.IsInInactivityLeak(helpers.PrevEpoch(state), state.FinalizedCheckpointEpoch()) { score := recoveryRate // Prevents underflow below 0. if score > v.InactivityScore { score = v.InactivityScore } v.InactivityScore -= score } inactivityScores[i] = v.InactivityScore } if err := state.SetInactivityScores(inactivityScores); err != nil { return nil, nil, err } return state, vals, nil } // ProcessEpochParticipation processes the epoch participation in state and updates individual validator's pre computes, // it also tracks and updates epoch attesting balances. func ProcessEpochParticipation( ctx context.Context, state state.BeaconState, bal *precompute.Balance, vals []*precompute.Validator, ) ([]*precompute.Validator, *precompute.Balance, error) { ctx, span := trace.StartSpan(ctx, "altair.ProcessEpochParticipation") defer span.End() cp, err := state.CurrentEpochParticipation() if err != nil { return nil, nil, err } cfg := params.BeaconConfig() targetIdx := cfg.TimelyTargetFlagIndex sourceIdx := cfg.TimelySourceFlagIndex headIdx := cfg.TimelyHeadFlagIndex for i, b := range cp { if HasValidatorFlag(b, targetIdx) { vals[i].IsCurrentEpochTargetAttester = true } } pp, err := state.PreviousEpochParticipation() if err != nil { return nil, nil, err } for i, b := range pp { if HasValidatorFlag(b, sourceIdx) { vals[i].IsPrevEpochAttester = true } if HasValidatorFlag(b, targetIdx) { vals[i].IsPrevEpochTargetAttester = true } if HasValidatorFlag(b, headIdx) { vals[i].IsPrevEpochHeadAttester = true } } bal = precompute.UpdateBalance(vals, bal) return vals, bal, nil }