2021-08-04 15:13:05 +00:00
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"
2021-09-30 19:00:14 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/core/time"
2021-08-04 15:13:05 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/state"
2021-09-21 19:59:25 +00:00
"github.com/prysmaticlabs/prysm/config/params"
2021-09-17 21:55:24 +00:00
"github.com/prysmaticlabs/prysm/math"
2021-12-08 19:02:53 +00:00
"github.com/prysmaticlabs/prysm/runtime/version"
2021-08-04 15:13:05 +00:00
"go.opencensus.io/trace"
)
2021-09-22 15:07:05 +00:00
// InitializePrecomputeValidators precomputes individual validator for its attested balances and the total sum of validators attested balances of the epoch.
func InitializePrecomputeValidators ( ctx context . Context , beaconState state . BeaconStateAltair ) ( [ ] * precompute . Validator , * precompute . Balance , error ) {
_ , span := trace . StartSpan ( ctx , "altair.InitializePrecomputeValidators" )
2021-08-04 15:13:05 +00:00
defer span . End ( )
2021-09-22 15:07:05 +00:00
vals := make ( [ ] * precompute . Validator , beaconState . NumValidators ( ) )
2021-08-04 15:13:05 +00:00
bal := & precompute . Balance { }
2021-09-30 19:00:14 +00:00
prevEpoch := time . PrevEpoch ( beaconState )
currentEpoch := time . CurrentEpoch ( beaconState )
2021-09-22 15:07:05 +00:00
inactivityScores , err := beaconState . InactivityScores ( )
2021-08-04 15:13:05 +00:00
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.
2021-09-22 15:07:05 +00:00
if beaconState . NumValidators ( ) != len ( inactivityScores ) {
return nil , nil , errors . New ( "num of validators is different than num of inactivity scores" )
2021-08-04 15:13:05 +00:00
}
2021-09-22 15:07:05 +00:00
if err := beaconState . ReadFromEveryValidator ( func ( idx int , val state . ReadOnlyValidator ) error {
// Set validator's balance, inactivity score and slashed/withdrawable status.
v := & precompute . Validator {
2021-08-04 15:13:05 +00:00
CurrentEpochEffectiveBalance : val . EffectiveBalance ( ) ,
InactivityScore : inactivityScores [ idx ] ,
2021-09-22 15:07:05 +00:00
IsSlashed : val . Slashed ( ) ,
IsWithdrawableCurrentEpoch : currentEpoch >= val . WithdrawableEpoch ( ) ,
2021-08-04 15:13:05 +00:00
}
2021-09-22 15:07:05 +00:00
// Set validator's active status for current epoch.
2021-08-04 15:13:05 +00:00
if helpers . IsActiveValidatorUsingTrie ( val , currentEpoch ) {
2021-09-22 15:07:05 +00:00
v . IsActiveCurrentEpoch = true
2021-10-20 18:06:43 +00:00
bal . ActiveCurrentEpoch , err = math . Add64 ( bal . ActiveCurrentEpoch , val . EffectiveBalance ( ) )
if err != nil {
return err
}
2021-08-04 15:13:05 +00:00
}
2021-09-22 15:07:05 +00:00
// Set validator's active status for preivous epoch.
2021-08-04 15:13:05 +00:00
if helpers . IsActiveValidatorUsingTrie ( val , prevEpoch ) {
2021-09-22 15:07:05 +00:00
v . IsActivePrevEpoch = true
2021-10-20 18:06:43 +00:00
bal . ActivePrevEpoch , err = math . Add64 ( bal . ActivePrevEpoch , val . EffectiveBalance ( ) )
if err != nil {
return err
}
2021-08-04 15:13:05 +00:00
}
2021-09-22 15:07:05 +00:00
vals [ idx ] = v
2021-08-04 15:13:05 +00:00
return nil
} ) ; err != nil {
2021-09-22 15:07:05 +00:00
return nil , nil , errors . Wrap ( err , "could not read every validator" )
2021-08-04 15:13:05 +00:00
}
2021-09-22 15:07:05 +00:00
return vals , bal , nil
2021-08-04 15:13:05 +00:00
}
// 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 ,
2021-09-22 15:07:05 +00:00
beaconState state . BeaconState ,
2021-08-04 15:13:05 +00:00
vals [ ] * precompute . Validator ,
) ( state . BeaconState , [ ] * precompute . Validator , error ) {
2021-08-29 18:57:37 +00:00
_ , span := trace . StartSpan ( ctx , "altair.ProcessInactivityScores" )
defer span . End ( )
2021-08-04 15:13:05 +00:00
cfg := params . BeaconConfig ( )
2021-09-30 19:00:14 +00:00
if time . CurrentEpoch ( beaconState ) == cfg . GenesisEpoch {
2021-09-22 15:07:05 +00:00
return beaconState , vals , nil
2021-08-04 15:13:05 +00:00
}
2021-09-22 15:07:05 +00:00
inactivityScores , err := beaconState . InactivityScores ( )
2021-08-04 15:13:05 +00:00
if err != nil {
return nil , nil , err
}
bias := cfg . InactivityScoreBias
recoveryRate := cfg . InactivityScoreRecoveryRate
2021-09-30 19:00:14 +00:00
prevEpoch := time . PrevEpoch ( beaconState )
2021-09-22 15:07:05 +00:00
finalizedEpoch := beaconState . FinalizedCheckpointEpoch ( )
2021-08-04 15:13:05 +00:00
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 {
2021-08-30 22:50:51 +00:00
v . InactivityScore -= 1
2021-08-04 15:13:05 +00:00
}
} else {
2021-09-28 22:19:10 +00:00
v . InactivityScore , err = math . Add64 ( v . InactivityScore , bias )
if err != nil {
return nil , nil , err
}
2021-08-04 15:13:05 +00:00
}
2021-08-24 01:42:05 +00:00
if ! helpers . IsInInactivityLeak ( prevEpoch , finalizedEpoch ) {
2021-08-04 15:13:05 +00:00
score := recoveryRate
// Prevents underflow below 0.
if score > v . InactivityScore {
score = v . InactivityScore
}
v . InactivityScore -= score
}
inactivityScores [ i ] = v . InactivityScore
}
2021-09-22 15:07:05 +00:00
if err := beaconState . SetInactivityScores ( inactivityScores ) ; err != nil {
2021-08-04 15:13:05 +00:00
return nil , nil , err
}
2021-09-22 15:07:05 +00:00
return beaconState , vals , nil
2021-08-04 15:13:05 +00:00
}
// ProcessEpochParticipation processes the epoch participation in state and updates individual validator's pre computes,
// it also tracks and updates epoch attesting balances.
2021-08-30 19:32:11 +00:00
// Spec code:
// if epoch == get_current_epoch(state):
// epoch_participation = state.current_epoch_participation
// else:
// epoch_participation = state.previous_epoch_participation
// active_validator_indices = get_active_validator_indices(state, epoch)
// participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)]
// return set(filter(lambda index: not state.validators[index].slashed, participating_indices))
2021-08-04 15:13:05 +00:00
func ProcessEpochParticipation (
ctx context . Context ,
2021-09-22 15:07:05 +00:00
beaconState state . BeaconState ,
2021-08-04 15:13:05 +00:00
bal * precompute . Balance ,
vals [ ] * precompute . Validator ,
) ( [ ] * precompute . Validator , * precompute . Balance , error ) {
2021-08-29 18:57:37 +00:00
_ , span := trace . StartSpan ( ctx , "altair.ProcessEpochParticipation" )
2021-08-04 15:13:05 +00:00
defer span . End ( )
2021-09-22 15:07:05 +00:00
cp , err := beaconState . CurrentEpochParticipation ( )
2021-08-04 15:13:05 +00:00
if err != nil {
return nil , nil , err
}
cfg := params . BeaconConfig ( )
targetIdx := cfg . TimelyTargetFlagIndex
sourceIdx := cfg . TimelySourceFlagIndex
headIdx := cfg . TimelyHeadFlagIndex
for i , b := range cp {
2021-11-01 10:36:08 +00:00
has , err := HasValidatorFlag ( b , sourceIdx )
if err != nil {
return nil , nil , err
}
if has && vals [ i ] . IsActiveCurrentEpoch {
vals [ i ] . IsCurrentEpochAttester = true
}
has , err = HasValidatorFlag ( b , targetIdx )
2021-10-21 15:01:09 +00:00
if err != nil {
return nil , nil , err
}
if has && vals [ i ] . IsActiveCurrentEpoch {
2021-11-01 10:36:08 +00:00
vals [ i ] . IsCurrentEpochAttester = true
2021-08-04 15:13:05 +00:00
vals [ i ] . IsCurrentEpochTargetAttester = true
}
}
2021-09-22 15:07:05 +00:00
pp , err := beaconState . PreviousEpochParticipation ( )
2021-08-04 15:13:05 +00:00
if err != nil {
return nil , nil , err
}
for i , b := range pp {
2021-10-21 15:01:09 +00:00
has , err := HasValidatorFlag ( b , sourceIdx )
if err != nil {
return nil , nil , err
}
if has && vals [ i ] . IsActivePrevEpoch {
2021-08-04 15:13:05 +00:00
vals [ i ] . IsPrevEpochAttester = true
2021-11-03 21:50:41 +00:00
vals [ i ] . IsPrevEpochSourceAttester = true
2021-08-04 15:13:05 +00:00
}
2021-10-21 15:01:09 +00:00
has , err = HasValidatorFlag ( b , targetIdx )
if err != nil {
return nil , nil , err
}
if has && vals [ i ] . IsActivePrevEpoch {
2021-11-03 21:50:41 +00:00
vals [ i ] . IsPrevEpochAttester = true
2021-08-04 15:13:05 +00:00
vals [ i ] . IsPrevEpochTargetAttester = true
}
2021-10-21 15:01:09 +00:00
has , err = HasValidatorFlag ( b , headIdx )
if err != nil {
return nil , nil , err
}
if has && vals [ i ] . IsActivePrevEpoch {
2021-08-04 15:13:05 +00:00
vals [ i ] . IsPrevEpochHeadAttester = true
}
}
2021-11-03 21:50:41 +00:00
bal = precompute . UpdateBalance ( vals , bal , beaconState . Version ( ) )
2021-08-04 15:13:05 +00:00
return vals , bal , nil
}
2021-08-06 20:02:35 +00:00
// ProcessRewardsAndPenaltiesPrecompute processes the rewards and penalties of individual validator.
// This is an optimized version by passing in precomputed validator attesting records and and total epoch balances.
func ProcessRewardsAndPenaltiesPrecompute (
2021-09-22 15:07:05 +00:00
beaconState state . BeaconStateAltair ,
2021-08-06 20:02:35 +00:00
bal * precompute . Balance ,
vals [ ] * precompute . Validator ,
) ( state . BeaconStateAltair , error ) {
// Don't process rewards and penalties in genesis epoch.
2021-09-22 15:07:05 +00:00
cfg := params . BeaconConfig ( )
2021-09-30 19:00:14 +00:00
if time . CurrentEpoch ( beaconState ) == cfg . GenesisEpoch {
2021-09-22 15:07:05 +00:00
return beaconState , nil
2021-08-06 20:02:35 +00:00
}
2021-09-22 15:07:05 +00:00
numOfVals := beaconState . NumValidators ( )
2021-08-06 20:02:35 +00:00
// Guard against an out-of-bounds using validator balance precompute.
2021-09-22 15:07:05 +00:00
if len ( vals ) != numOfVals || len ( vals ) != beaconState . BalancesLength ( ) {
return beaconState , errors . New ( "validator registries not the same length as state's validator registries" )
2021-08-06 20:02:35 +00:00
}
2021-09-22 15:07:05 +00:00
attsRewards , attsPenalties , err := AttestationsDelta ( beaconState , bal , vals )
2021-08-06 20:02:35 +00:00
if err != nil {
return nil , errors . Wrap ( err , "could not get attestation delta" )
}
2021-09-22 15:07:05 +00:00
balances := beaconState . Balances ( )
2021-08-06 20:02:35 +00:00
for i := 0 ; i < numOfVals ; i ++ {
vals [ i ] . BeforeEpochTransitionBalance = balances [ i ]
// Compute the post balance of the validator after accounting for the
// attester and proposer rewards and penalties.
2021-09-28 22:19:10 +00:00
balances [ i ] , err = helpers . IncreaseBalanceWithVal ( balances [ i ] , attsRewards [ i ] )
if err != nil {
return nil , err
}
2021-08-06 20:02:35 +00:00
balances [ i ] = helpers . DecreaseBalanceWithVal ( balances [ i ] , attsPenalties [ i ] )
vals [ i ] . AfterEpochTransitionBalance = balances [ i ]
}
2021-09-22 15:07:05 +00:00
if err := beaconState . SetBalances ( balances ) ; err != nil {
2021-08-06 20:02:35 +00:00
return nil , errors . Wrap ( err , "could not set validator balances" )
}
2021-09-22 15:07:05 +00:00
return beaconState , nil
2021-08-06 20:02:35 +00:00
}
// AttestationsDelta computes and returns the rewards and penalties differences for individual validators based on the
// voting records.
2021-12-08 19:02:53 +00:00
func AttestationsDelta ( beaconState state . BeaconState , bal * precompute . Balance , vals [ ] * precompute . Validator ) ( rewards , penalties [ ] uint64 , err error ) {
2021-09-22 15:07:05 +00:00
numOfVals := beaconState . NumValidators ( )
2021-08-06 20:02:35 +00:00
rewards = make ( [ ] uint64 , numOfVals )
penalties = make ( [ ] uint64 , numOfVals )
cfg := params . BeaconConfig ( )
2021-09-30 19:00:14 +00:00
prevEpoch := time . PrevEpoch ( beaconState )
2021-09-22 15:07:05 +00:00
finalizedEpoch := beaconState . FinalizedCheckpointEpoch ( )
2021-08-06 20:02:35 +00:00
increment := cfg . EffectiveBalanceIncrement
factor := cfg . BaseRewardFactor
2021-09-17 21:55:24 +00:00
baseRewardMultiplier := increment * factor / math . IntegerSquareRoot ( bal . ActiveCurrentEpoch )
2021-08-06 20:02:35 +00:00
leak := helpers . IsInInactivityLeak ( prevEpoch , finalizedEpoch )
2021-12-08 19:02:53 +00:00
2022-01-10 16:47:30 +00:00
// Modified in Altair and Bellatrix.
2021-12-08 19:02:53 +00:00
var inactivityDenominator uint64
bias := cfg . InactivityScoreBias
switch beaconState . Version ( ) {
case version . Altair :
inactivityDenominator = bias * cfg . InactivityPenaltyQuotientAltair
2022-01-10 16:47:30 +00:00
case version . Bellatrix :
2022-01-26 07:24:47 +00:00
inactivityDenominator = bias * cfg . InactivityPenaltyQuotientBellatrix
2021-12-08 19:02:53 +00:00
default :
return nil , nil , errors . Errorf ( "invalid state type version: %T" , beaconState . Version ( ) )
}
2021-08-06 20:02:35 +00:00
for i , v := range vals {
2021-09-28 22:19:10 +00:00
rewards [ i ] , penalties [ i ] , err = attestationDelta ( bal , v , baseRewardMultiplier , inactivityDenominator , leak )
if err != nil {
return nil , nil , err
}
2021-08-06 20:02:35 +00:00
}
return rewards , penalties , nil
}
func attestationDelta (
bal * precompute . Balance ,
2021-09-22 15:07:05 +00:00
val * precompute . Validator ,
2021-08-06 20:02:35 +00:00
baseRewardMultiplier , inactivityDenominator uint64 ,
2021-09-28 22:19:10 +00:00
inactivityLeak bool ) ( reward , penalty uint64 , err error ) {
2021-09-22 15:07:05 +00:00
eligible := val . IsActivePrevEpoch || ( val . IsSlashed && ! val . IsWithdrawableCurrentEpoch )
2021-08-06 20:02:35 +00:00
// Per spec `ActiveCurrentEpoch` can't be 0 to process attestation delta.
if ! eligible || bal . ActiveCurrentEpoch == 0 {
2021-09-28 22:19:10 +00:00
return 0 , 0 , nil
2021-08-06 20:02:35 +00:00
}
cfg := params . BeaconConfig ( )
increment := cfg . EffectiveBalanceIncrement
2021-09-22 15:07:05 +00:00
effectiveBalance := val . CurrentEpochEffectiveBalance
2021-08-06 20:02:35 +00:00
baseReward := ( effectiveBalance / increment ) * baseRewardMultiplier
activeIncrement := bal . ActiveCurrentEpoch / increment
weightDenominator := cfg . WeightDenominator
srcWeight := cfg . TimelySourceWeight
tgtWeight := cfg . TimelyTargetWeight
headWeight := cfg . TimelyHeadWeight
reward , penalty = uint64 ( 0 ) , uint64 ( 0 )
// Process source reward / penalty
2021-11-03 21:50:41 +00:00
if val . IsPrevEpochSourceAttester && ! val . IsSlashed {
2021-08-06 20:02:35 +00:00
if ! inactivityLeak {
n := baseReward * srcWeight * ( bal . PrevEpochAttested / increment )
reward += n / ( activeIncrement * weightDenominator )
}
} else {
penalty += baseReward * srcWeight / weightDenominator
}
// Process target reward / penalty
2021-09-22 15:07:05 +00:00
if val . IsPrevEpochTargetAttester && ! val . IsSlashed {
2021-08-06 20:02:35 +00:00
if ! inactivityLeak {
n := baseReward * tgtWeight * ( bal . PrevEpochTargetAttested / increment )
reward += n / ( activeIncrement * weightDenominator )
}
} else {
penalty += baseReward * tgtWeight / weightDenominator
}
// Process head reward / penalty
2021-09-22 15:07:05 +00:00
if val . IsPrevEpochHeadAttester && ! val . IsSlashed {
2021-08-06 20:02:35 +00:00
if ! inactivityLeak {
n := baseReward * headWeight * ( bal . PrevEpochHeadAttested / increment )
reward += n / ( activeIncrement * weightDenominator )
}
}
// Process finality delay penalty
// Apply an additional penalty to validators that did not vote on the correct target or slashed
2021-09-22 15:07:05 +00:00
if ! val . IsPrevEpochTargetAttester || val . IsSlashed {
2021-09-28 22:19:10 +00:00
n , err := math . Mul64 ( effectiveBalance , val . InactivityScore )
if err != nil {
return 0 , 0 , err
}
2021-08-06 20:02:35 +00:00
penalty += n / inactivityDenominator
}
2021-09-28 22:19:10 +00:00
return reward , penalty , nil
2021-08-06 20:02:35 +00:00
}