github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/core/validators/validator.go (about) 1 // Package validators contains libraries to shuffle validators 2 // and retrieve active validator indices from a given slot 3 // or an attestation. It also provides helper functions to locate 4 // validator based on pubic key. 5 package validators 6 7 import ( 8 "github.com/pkg/errors" 9 types "github.com/prysmaticlabs/eth2-types" 10 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 11 iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface" 12 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 13 "github.com/prysmaticlabs/prysm/shared/params" 14 ) 15 16 // InitiateValidatorExit takes in validator index and updates 17 // validator with correct voluntary exit parameters. 18 // 19 // Spec pseudocode definition: 20 // def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: 21 // """ 22 // Initiate the exit of the validator with index ``index``. 23 // """ 24 // # Return if validator already initiated exit 25 // validator = state.validators[index] 26 // if validator.exit_epoch != FAR_FUTURE_EPOCH: 27 // return 28 // 29 // # Compute exit queue epoch 30 // exit_epochs = [v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH] 31 // exit_queue_epoch = max(exit_epochs + [compute_activation_exit_epoch(get_current_epoch(state))]) 32 // exit_queue_churn = len([v for v in state.validators if v.exit_epoch == exit_queue_epoch]) 33 // if exit_queue_churn >= get_validator_churn_limit(state): 34 // exit_queue_epoch += Epoch(1) 35 // 36 // # Set validator exit epoch and withdrawable epoch 37 // validator.exit_epoch = exit_queue_epoch 38 // validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) 39 func InitiateValidatorExit(state iface.BeaconState, idx types.ValidatorIndex) (iface.BeaconState, error) { 40 validator, err := state.ValidatorAtIndex(idx) 41 if err != nil { 42 return nil, err 43 } 44 if validator.ExitEpoch != params.BeaconConfig().FarFutureEpoch { 45 return state, nil 46 } 47 var exitEpochs []types.Epoch 48 err = state.ReadFromEveryValidator(func(idx int, val iface.ReadOnlyValidator) error { 49 if val.ExitEpoch() != params.BeaconConfig().FarFutureEpoch { 50 exitEpochs = append(exitEpochs, val.ExitEpoch()) 51 } 52 return nil 53 }) 54 if err != nil { 55 return nil, err 56 } 57 exitEpochs = append(exitEpochs, helpers.ActivationExitEpoch(helpers.CurrentEpoch(state))) 58 59 // Obtain the exit queue epoch as the maximum number in the exit epochs array. 60 exitQueueEpoch := types.Epoch(0) 61 for _, i := range exitEpochs { 62 if exitQueueEpoch < i { 63 exitQueueEpoch = i 64 } 65 } 66 67 // We use the exit queue churn to determine if we have passed a churn limit. 68 exitQueueChurn := uint64(0) 69 err = state.ReadFromEveryValidator(func(idx int, val iface.ReadOnlyValidator) error { 70 if val.ExitEpoch() == exitQueueEpoch { 71 exitQueueChurn++ 72 } 73 return nil 74 }) 75 if err != nil { 76 return nil, err 77 } 78 activeValidatorCount, err := helpers.ActiveValidatorCount(state, helpers.CurrentEpoch(state)) 79 if err != nil { 80 return nil, errors.Wrap(err, "could not get active validator count") 81 } 82 churn, err := helpers.ValidatorChurnLimit(activeValidatorCount) 83 if err != nil { 84 return nil, errors.Wrap(err, "could not get churn limit") 85 } 86 87 if exitQueueChurn >= churn { 88 exitQueueEpoch++ 89 } 90 validator.ExitEpoch = exitQueueEpoch 91 validator.WithdrawableEpoch = exitQueueEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay 92 if err := state.UpdateValidatorAtIndex(idx, validator); err != nil { 93 return nil, err 94 } 95 return state, nil 96 } 97 98 // SlashValidator slashes the malicious validator's balance and awards 99 // the whistleblower's balance. 100 // 101 // Spec pseudocode definition: 102 // def slash_validator(state: BeaconState, 103 // slashed_index: ValidatorIndex, 104 // whistleblower_index: ValidatorIndex=None) -> None: 105 // """ 106 // Slash the validator with index ``slashed_index``. 107 // """ 108 // epoch = get_current_epoch(state) 109 // initiate_validator_exit(state, slashed_index) 110 // validator = state.validators[slashed_index] 111 // validator.slashed = True 112 // validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) 113 // state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance 114 // decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT) 115 // 116 // # Apply proposer and whistleblower rewards 117 // proposer_index = get_beacon_proposer_index(state) 118 // if whistleblower_index is None: 119 // whistleblower_index = proposer_index 120 // whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) 121 // proposer_reward = Gwei(whistleblower_reward // PROPOSER_REWARD_QUOTIENT) 122 // increase_balance(state, proposer_index, proposer_reward) 123 // increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) 124 func SlashValidator(state iface.BeaconState, slashedIdx types.ValidatorIndex) (iface.BeaconState, error) { 125 state, err := InitiateValidatorExit(state, slashedIdx) 126 if err != nil { 127 return nil, errors.Wrapf(err, "could not initiate validator %d exit", slashedIdx) 128 } 129 currentEpoch := helpers.SlotToEpoch(state.Slot()) 130 validator, err := state.ValidatorAtIndex(slashedIdx) 131 if err != nil { 132 return nil, err 133 } 134 validator.Slashed = true 135 maxWithdrawableEpoch := types.MaxEpoch(validator.WithdrawableEpoch, currentEpoch+params.BeaconConfig().EpochsPerSlashingsVector) 136 validator.WithdrawableEpoch = maxWithdrawableEpoch 137 138 if err := state.UpdateValidatorAtIndex(slashedIdx, validator); err != nil { 139 return nil, err 140 } 141 142 // The slashing amount is represented by epochs per slashing vector. The validator's effective balance is then applied to that amount. 143 slashings := state.Slashings() 144 currentSlashing := slashings[currentEpoch%params.BeaconConfig().EpochsPerSlashingsVector] 145 if err := state.UpdateSlashingsAtIndex( 146 uint64(currentEpoch%params.BeaconConfig().EpochsPerSlashingsVector), 147 currentSlashing+validator.EffectiveBalance, 148 ); err != nil { 149 return nil, err 150 } 151 if err := helpers.DecreaseBalance(state, slashedIdx, validator.EffectiveBalance/params.BeaconConfig().MinSlashingPenaltyQuotient); err != nil { 152 return nil, err 153 } 154 155 proposerIdx, err := helpers.BeaconProposerIndex(state) 156 if err != nil { 157 return nil, errors.Wrap(err, "could not get proposer idx") 158 } 159 // In phase 0, the proposer is the whistleblower. 160 whistleBlowerIdx := proposerIdx 161 whistleblowerReward := validator.EffectiveBalance / params.BeaconConfig().WhistleBlowerRewardQuotient 162 proposerReward := whistleblowerReward / params.BeaconConfig().ProposerRewardQuotient 163 err = helpers.IncreaseBalance(state, proposerIdx, proposerReward) 164 if err != nil { 165 return nil, err 166 } 167 err = helpers.IncreaseBalance(state, whistleBlowerIdx, whistleblowerReward-proposerReward) 168 if err != nil { 169 return nil, err 170 } 171 return state, nil 172 } 173 174 // ActivatedValidatorIndices determines the indices activated during the given epoch. 175 func ActivatedValidatorIndices(epoch types.Epoch, validators []*ethpb.Validator) []types.ValidatorIndex { 176 activations := make([]types.ValidatorIndex, 0) 177 for i := 0; i < len(validators); i++ { 178 val := validators[i] 179 if val.ActivationEpoch <= epoch && epoch < val.ExitEpoch { 180 activations = append(activations, types.ValidatorIndex(i)) 181 } 182 } 183 return activations 184 } 185 186 // SlashedValidatorIndices determines the indices slashed during the given epoch. 187 func SlashedValidatorIndices(epoch types.Epoch, validators []*ethpb.Validator) []types.ValidatorIndex { 188 slashed := make([]types.ValidatorIndex, 0) 189 for i := 0; i < len(validators); i++ { 190 val := validators[i] 191 maxWithdrawableEpoch := types.MaxEpoch(val.WithdrawableEpoch, epoch+params.BeaconConfig().EpochsPerSlashingsVector) 192 if val.WithdrawableEpoch == maxWithdrawableEpoch && val.Slashed { 193 slashed = append(slashed, types.ValidatorIndex(i)) 194 } 195 } 196 return slashed 197 } 198 199 // ExitedValidatorIndices determines the indices exited during the current epoch. 200 func ExitedValidatorIndices(epoch types.Epoch, validators []*ethpb.Validator, activeValidatorCount uint64) ([]types.ValidatorIndex, error) { 201 exited := make([]types.ValidatorIndex, 0) 202 exitEpochs := make([]types.Epoch, 0) 203 for i := 0; i < len(validators); i++ { 204 val := validators[i] 205 if val.ExitEpoch != params.BeaconConfig().FarFutureEpoch { 206 exitEpochs = append(exitEpochs, val.ExitEpoch) 207 } 208 } 209 exitQueueEpoch := types.Epoch(0) 210 for _, i := range exitEpochs { 211 if exitQueueEpoch < i { 212 exitQueueEpoch = i 213 } 214 } 215 216 // We use the exit queue churn to determine if we have passed a churn limit. 217 exitQueueChurn := uint64(0) 218 for _, val := range validators { 219 if val.ExitEpoch == exitQueueEpoch { 220 exitQueueChurn++ 221 } 222 } 223 churn, err := helpers.ValidatorChurnLimit(activeValidatorCount) 224 if err != nil { 225 return nil, errors.Wrap(err, "could not get churn limit") 226 } 227 if churn < exitQueueChurn { 228 exitQueueEpoch++ 229 } 230 withdrawableEpoch := exitQueueEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay 231 for i, val := range validators { 232 if val.ExitEpoch == epoch && val.WithdrawableEpoch == withdrawableEpoch && 233 val.EffectiveBalance > params.BeaconConfig().EjectionBalance { 234 exited = append(exited, types.ValidatorIndex(i)) 235 } 236 } 237 return exited, nil 238 } 239 240 // EjectedValidatorIndices determines the indices ejected during the given epoch. 241 func EjectedValidatorIndices(epoch types.Epoch, validators []*ethpb.Validator, activeValidatorCount uint64) ([]types.ValidatorIndex, error) { 242 ejected := make([]types.ValidatorIndex, 0) 243 exitEpochs := make([]types.Epoch, 0) 244 for i := 0; i < len(validators); i++ { 245 val := validators[i] 246 if val.ExitEpoch != params.BeaconConfig().FarFutureEpoch { 247 exitEpochs = append(exitEpochs, val.ExitEpoch) 248 } 249 } 250 exitQueueEpoch := types.Epoch(0) 251 for _, i := range exitEpochs { 252 if exitQueueEpoch < i { 253 exitQueueEpoch = i 254 } 255 } 256 257 // We use the exit queue churn to determine if we have passed a churn limit. 258 exitQueueChurn := uint64(0) 259 for _, val := range validators { 260 if val.ExitEpoch == exitQueueEpoch { 261 exitQueueChurn++ 262 } 263 } 264 churn, err := helpers.ValidatorChurnLimit(activeValidatorCount) 265 if err != nil { 266 return nil, errors.Wrap(err, "could not get churn limit") 267 } 268 if churn < exitQueueChurn { 269 exitQueueEpoch++ 270 } 271 withdrawableEpoch := exitQueueEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay 272 for i, val := range validators { 273 if val.ExitEpoch == epoch && val.WithdrawableEpoch == withdrawableEpoch && 274 val.EffectiveBalance <= params.BeaconConfig().EjectionBalance { 275 ejected = append(ejected, types.ValidatorIndex(i)) 276 } 277 } 278 return ejected, nil 279 }