// Package epoch contains epoch processing libraries according to spec, able to // process new balance for validators, justify and finalize new // check points, and shuffle validators to different slots and // shards. package epoch import ( "fmt" "sort" "github.com/pkg/errors" types "github.com/prysmaticlabs/eth2-types" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/validators" iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateV0" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" "github.com/prysmaticlabs/prysm/shared/attestationutil" "github.com/prysmaticlabs/prysm/shared/mathutil" "github.com/prysmaticlabs/prysm/shared/params" ) // sortableIndices implements the Sort interface to sort newly activated validator indices // by activation epoch and by index number. type sortableIndices struct { indices []types.ValidatorIndex validators []*ethpb.Validator } func (s sortableIndices) Len() int { return len(s.indices) } func (s sortableIndices) Swap(i, j int) { s.indices[i], s.indices[j] = s.indices[j], s.indices[i] } func (s sortableIndices) Less(i, j int) bool { if s.validators[s.indices[i]].ActivationEligibilityEpoch == s.validators[s.indices[j]].ActivationEligibilityEpoch { return s.indices[i] < s.indices[j] } return s.validators[s.indices[i]].ActivationEligibilityEpoch < s.validators[s.indices[j]].ActivationEligibilityEpoch } // AttestingBalance returns the total balance from all the attesting indices. // // WARNING: This method allocates a new copy of the attesting validator indices set and is // considered to be very memory expensive. Avoid using this unless you really // need to get attesting balance from attestations. // // Spec pseudocode definition: // def get_attesting_balance(state: BeaconState, attestations: Sequence[PendingAttestation]) -> Gwei: // """ // Return the combined effective balance of the set of unslashed validators participating in ``attestations``. // Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. // """ // return get_total_balance(state, get_unslashed_attesting_indices(state, attestations)) func AttestingBalance(state iface.ReadOnlyBeaconState, atts []*pb.PendingAttestation) (uint64, error) { indices, err := UnslashedAttestingIndices(state, atts) if err != nil { return 0, errors.Wrap(err, "could not get attesting indices") } return helpers.TotalBalance(state, indices), nil } // ProcessRegistryUpdates rotates validators in and out of active pool. // the amount to rotate is determined churn limit. // // Spec pseudocode definition: // def process_registry_updates(state: BeaconState) -> None: // # Process activation eligibility and ejections // for index, validator in enumerate(state.validators): // if is_eligible_for_activation_queue(validator): // validator.activation_eligibility_epoch = get_current_epoch(state) + 1 // // if is_active_validator(validator, get_current_epoch(state)) and validator.effective_balance <= EJECTION_BALANCE: // initiate_validator_exit(state, ValidatorIndex(index)) // // # Queue validators eligible for activation and not yet dequeued for activation // activation_queue = sorted([ // index for index, validator in enumerate(state.validators) // if is_eligible_for_activation(state, validator) // # Order by the sequence of activation_eligibility_epoch setting and then index // ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) // # Dequeued validators for activation up to churn limit // for index in activation_queue[:get_validator_churn_limit(state)]: // validator = state.validators[index] // validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) func ProcessRegistryUpdates(state iface.BeaconState) (iface.BeaconState, error) { currentEpoch := helpers.CurrentEpoch(state) vals := state.Validators() var err error ejectionBal := params.BeaconConfig().EjectionBalance activationEligibilityEpoch := helpers.CurrentEpoch(state) + 1 for idx, validator := range vals { // Process the validators for activation eligibility. if helpers.IsEligibleForActivationQueue(validator) { validator.ActivationEligibilityEpoch = activationEligibilityEpoch if err := state.UpdateValidatorAtIndex(types.ValidatorIndex(idx), validator); err != nil { return nil, err } } // Process the validators for ejection. isActive := helpers.IsActiveValidator(validator, currentEpoch) belowEjectionBalance := validator.EffectiveBalance <= ejectionBal if isActive && belowEjectionBalance { state, err = validators.InitiateValidatorExit(state, types.ValidatorIndex(idx)) if err != nil { return nil, errors.Wrapf(err, "could not initiate exit for validator %d", idx) } } } // Queue validators eligible for activation and not yet dequeued for activation. var activationQ []types.ValidatorIndex for idx, validator := range vals { if helpers.IsEligibleForActivation(state, validator) { activationQ = append(activationQ, types.ValidatorIndex(idx)) } } sort.Sort(sortableIndices{indices: activationQ, validators: vals}) // Only activate just enough validators according to the activation churn limit. limit := uint64(len(activationQ)) activeValidatorCount, err := helpers.ActiveValidatorCount(state, currentEpoch) if err != nil { return nil, errors.Wrap(err, "could not get active validator count") } churnLimit, err := helpers.ValidatorChurnLimit(activeValidatorCount) if err != nil { return nil, errors.Wrap(err, "could not get churn limit") } // Prevent churn limit cause index out of bound. if churnLimit < limit { limit = churnLimit } activationExitEpoch := helpers.ActivationExitEpoch(currentEpoch) for _, index := range activationQ[:limit] { validator, err := state.ValidatorAtIndex(index) if err != nil { return nil, err } validator.ActivationEpoch = activationExitEpoch if err := state.UpdateValidatorAtIndex(index, validator); err != nil { return nil, err } } return state, nil } // ProcessSlashings processes the slashed validators during epoch processing, // // def process_slashings(state: BeaconState) -> None: // epoch = get_current_epoch(state) // total_balance = get_total_active_balance(state) // adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance) // for index, validator in enumerate(state.validators): // if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: // increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow // penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance // penalty = penalty_numerator // total_balance * increment // decrease_balance(state, ValidatorIndex(index), penalty) func ProcessSlashings(state iface.BeaconState) (iface.BeaconState, error) { currentEpoch := helpers.CurrentEpoch(state) totalBalance, err := helpers.TotalActiveBalance(state) if err != nil { return nil, errors.Wrap(err, "could not get total active balance") } // Compute slashed balances in the current epoch exitLength := params.BeaconConfig().EpochsPerSlashingsVector // Compute the sum of state slashings slashings := state.Slashings() totalSlashing := uint64(0) for _, slashing := range slashings { totalSlashing += slashing } // a callback is used here to apply the following actions to all validators // below equally. increment := params.BeaconConfig().EffectiveBalanceIncrement minSlashing := mathutil.Min(totalSlashing*params.BeaconConfig().ProportionalSlashingMultiplier, totalBalance) err = state.ApplyToEveryValidator(func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) { correctEpoch := (currentEpoch + exitLength/2) == val.WithdrawableEpoch if val.Slashed && correctEpoch { penaltyNumerator := val.EffectiveBalance / increment * minSlashing penalty := penaltyNumerator / totalBalance * increment if err := helpers.DecreaseBalance(state, types.ValidatorIndex(idx), penalty); err != nil { return false, val, err } return true, val, nil } return false, val, nil }) return state, err } // ProcessEth1DataReset processes updates to ETH1 data votes during epoch processing. // // Spec pseudocode definition: // def process_eth1_data_reset(state: BeaconState) -> None: // next_epoch = Epoch(get_current_epoch(state) + 1) // # Reset eth1 data votes // if next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0: // state.eth1_data_votes = [] func ProcessEth1DataReset(state iface.BeaconState) (iface.BeaconState, error) { currentEpoch := helpers.CurrentEpoch(state) nextEpoch := currentEpoch + 1 // Reset ETH1 data votes. if nextEpoch%params.BeaconConfig().EpochsPerEth1VotingPeriod == 0 { if err := state.SetEth1DataVotes([]*ethpb.Eth1Data{}); err != nil { return nil, err } } return state, nil } // ProcessEffectiveBalanceUpdates processes effective balance updates during epoch processing. // // Spec pseudocode definition: // def process_effective_balance_updates(state: BeaconState) -> None: // # Update effective balances with hysteresis // for index, validator in enumerate(state.validators): // balance = state.balances[index] // HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) // DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER // UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER // if ( // balance + DOWNWARD_THRESHOLD < validator.effective_balance // or validator.effective_balance + UPWARD_THRESHOLD < balance // ): // validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) func ProcessEffectiveBalanceUpdates(state iface.BeaconState) (iface.BeaconState, error) { effBalanceInc := params.BeaconConfig().EffectiveBalanceIncrement maxEffBalance := params.BeaconConfig().MaxEffectiveBalance hysteresisInc := effBalanceInc / params.BeaconConfig().HysteresisQuotient downwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisDownwardMultiplier upwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisUpwardMultiplier bals := state.Balances() // Update effective balances with hysteresis. validatorFunc := func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) { if val == nil { return false, nil, fmt.Errorf("validator %d is nil in state", idx) } if idx >= len(bals) { return false, nil, fmt.Errorf("validator index exceeds validator length in state %d >= %d", idx, len(state.Balances())) } balance := bals[idx] if balance+downwardThreshold < val.EffectiveBalance || val.EffectiveBalance+upwardThreshold < balance { effectiveBal := maxEffBalance if effectiveBal > balance-balance%effBalanceInc { effectiveBal = balance - balance%effBalanceInc } if effectiveBal != val.EffectiveBalance { newVal := stateV0.CopyValidator(val) newVal.EffectiveBalance = effectiveBal return true, newVal, nil } return false, val, nil } return false, val, nil } if err := state.ApplyToEveryValidator(validatorFunc); err != nil { return nil, err } return state, nil } // ProcessSlashingsReset processes the total slashing balances updates during epoch processing. // // Spec pseudocode definition: // def process_slashings_reset(state: BeaconState) -> None: // next_epoch = Epoch(get_current_epoch(state) + 1) // # Reset slashings // state.slashings[next_epoch % EPOCHS_PER_SLASHINGS_VECTOR] = Gwei(0) func ProcessSlashingsReset(state iface.BeaconState) (iface.BeaconState, error) { currentEpoch := helpers.CurrentEpoch(state) nextEpoch := currentEpoch + 1 // Set total slashed balances. slashedExitLength := params.BeaconConfig().EpochsPerSlashingsVector slashedEpoch := nextEpoch % slashedExitLength slashings := state.Slashings() if uint64(len(slashings)) != uint64(slashedExitLength) { return nil, fmt.Errorf( "state slashing length %d different than EpochsPerHistoricalVector %d", len(slashings), slashedExitLength, ) } if err := state.UpdateSlashingsAtIndex(uint64(slashedEpoch) /* index */, 0 /* value */); err != nil { return nil, err } return state, nil } // ProcessRandaoMixesReset processes the final updates to RANDAO mix during epoch processing. // // Spec pseudocode definition: // def process_randao_mixes_reset(state: BeaconState) -> None: // current_epoch = get_current_epoch(state) // next_epoch = Epoch(current_epoch + 1) // # Set randao mix // state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) func ProcessRandaoMixesReset(state iface.BeaconState) (iface.BeaconState, error) { currentEpoch := helpers.CurrentEpoch(state) nextEpoch := currentEpoch + 1 // Set RANDAO mix. randaoMixLength := params.BeaconConfig().EpochsPerHistoricalVector if uint64(state.RandaoMixesLength()) != uint64(randaoMixLength) { return nil, fmt.Errorf( "state randao length %d different than EpochsPerHistoricalVector %d", state.RandaoMixesLength(), randaoMixLength, ) } mix, err := helpers.RandaoMix(state, currentEpoch) if err != nil { return nil, err } if err := state.UpdateRandaoMixesAtIndex(uint64(nextEpoch%randaoMixLength), mix); err != nil { return nil, err } return state, nil } // ProcessHistoricalRootsUpdate processes the updates to historical root accumulator during epoch processing. // // Spec pseudocode definition: // def process_historical_roots_update(state: BeaconState) -> None: // # Set historical root accumulator // next_epoch = Epoch(get_current_epoch(state) + 1) // if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: // historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) // state.historical_roots.append(hash_tree_root(historical_batch)) func ProcessHistoricalRootsUpdate(state iface.BeaconState) (iface.BeaconState, error) { currentEpoch := helpers.CurrentEpoch(state) nextEpoch := currentEpoch + 1 // Set historical root accumulator. epochsPerHistoricalRoot := params.BeaconConfig().SlotsPerHistoricalRoot.DivSlot(params.BeaconConfig().SlotsPerEpoch) if nextEpoch.Mod(uint64(epochsPerHistoricalRoot)) == 0 { historicalBatch := &pb.HistoricalBatch{ BlockRoots: state.BlockRoots(), StateRoots: state.StateRoots(), } batchRoot, err := historicalBatch.HashTreeRoot() if err != nil { return nil, errors.Wrap(err, "could not hash historical batch") } if err := state.AppendHistoricalRoots(batchRoot); err != nil { return nil, err } } return state, nil } // ProcessParticipationRecordUpdates rotates current/previous epoch attestations during epoch processing. // // Spec pseudocode definition: // def process_participation_record_updates(state: BeaconState) -> None: // # Rotate current/previous epoch attestations // state.previous_epoch_attestations = state.current_epoch_attestations // state.current_epoch_attestations = [] func ProcessParticipationRecordUpdates(state iface.BeaconState) (iface.BeaconState, error) { if err := state.RotateAttestations(); err != nil { return nil, err } return state, nil } // ProcessFinalUpdates processes the final updates during epoch processing. func ProcessFinalUpdates(state iface.BeaconState) (iface.BeaconState, error) { var err error // Reset ETH1 data votes. state, err = ProcessEth1DataReset(state) if err != nil { return nil, err } // Update effective balances with hysteresis. state, err = ProcessEffectiveBalanceUpdates(state) if err != nil { return nil, err } // Set total slashed balances. state, err = ProcessSlashingsReset(state) if err != nil { return nil, err } // Set RANDAO mix. state, err = ProcessRandaoMixesReset(state) if err != nil { return nil, err } // Set historical root accumulator. state, err = ProcessHistoricalRootsUpdate(state) if err != nil { return nil, err } // Rotate current and previous epoch attestations. state, err = ProcessParticipationRecordUpdates(state) if err != nil { return nil, err } return state, nil } // UnslashedAttestingIndices returns all the attesting indices from a list of attestations, // it sorts the indices and filters out the slashed ones. // // Spec pseudocode definition: // def get_unslashed_attesting_indices(state: BeaconState, // attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: // output = set() # type: Set[ValidatorIndex] // for a in attestations: // output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) // return set(filter(lambda index: not state.validators[index].slashed, output)) func UnslashedAttestingIndices(state iface.ReadOnlyBeaconState, atts []*pb.PendingAttestation) ([]types.ValidatorIndex, error) { var setIndices []types.ValidatorIndex seen := make(map[uint64]bool) for _, att := range atts { committee, err := helpers.BeaconCommitteeFromState(state, att.Data.Slot, att.Data.CommitteeIndex) if err != nil { return nil, err } attestingIndices, err := attestationutil.AttestingIndices(att.AggregationBits, committee) if err != nil { return nil, err } // Create a set for attesting indices for _, index := range attestingIndices { if !seen[index] { setIndices = append(setIndices, types.ValidatorIndex(index)) } seen[index] = true } } // Sort the attesting set indices by increasing order. sort.Slice(setIndices, func(i, j int) bool { return setIndices[i] < setIndices[j] }) // Remove the slashed validator indices. for i := 0; i < len(setIndices); i++ { v, err := state.ValidatorAtIndexReadOnly(setIndices[i]) if err != nil { return nil, errors.Wrap(err, "failed to look up validator") } if !v.IsNil() && v.Slashed() { setIndices = append(setIndices[:i], setIndices[i+1:]...) } } return setIndices, nil } // BaseReward takes state and validator index and calculate // individual validator's base reward quotient. // // Note: Adjusted quotient is calculated of base reward because it's too inefficient // to repeat the same calculation for every validator versus just doing it once. // // Spec pseudocode definition: // def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: // total_balance = get_total_active_balance(state) // effective_balance = state.validators[index].effective_balance // return Gwei(effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH) func BaseReward(state iface.ReadOnlyBeaconState, index types.ValidatorIndex) (uint64, error) { totalBalance, err := helpers.TotalActiveBalance(state) if err != nil { return 0, errors.Wrap(err, "could not calculate active balance") } val, err := state.ValidatorAtIndexReadOnly(index) if err != nil { return 0, err } effectiveBalance := val.EffectiveBalance() baseReward := effectiveBalance * params.BeaconConfig().BaseRewardFactor / mathutil.IntegerSquareRoot(totalBalance) / params.BeaconConfig().BaseRewardsPerEpoch return baseReward, nil }