// Package balances contains libraries to calculate reward and // penalty quotients. It computes new validator balances // for justifications, crosslinks and attestation inclusions. It // also computes penalties for the inactive validators. package balances import ( "fmt" "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch" "github.com/prysmaticlabs/prysm/beacon-chain/core/validators" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" "github.com/prysmaticlabs/prysm/shared/mathutil" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/sliceutil" ) var config = params.BeaconConfig() // ExpectedFFGSource applies rewards or penalties // for an expected FFG source. It uses total justified // attesting balances, total validator balances and base // reward quotient to calculate the reward amount. // Validators who voted for previous justified hash // will get a reward, everyone else will get a penalty. // // Spec pseudocode definition: // Any validator index in previous_epoch_justified_attester_indices // gains base_reward(state, index) * previous_epoch_justified_attesting_balance // total_balance. // Any active validator v not in previous_epoch_justified_attester_indices // loses base_reward(state, index). func ExpectedFFGSource( state *pb.BeaconState, justifiedAttesterIndices []uint64, justifiedAttestingBalance uint64, totalBalance uint64) *pb.BeaconState { baseRewardQuotient := baseRewardQuotient(totalBalance) for _, index := range justifiedAttesterIndices { state.ValidatorBalances[index] += baseReward(state, index, baseRewardQuotient) * justifiedAttestingBalance / totalBalance } activeValidatorIndices := validators.ActiveValidatorIndices(state.ValidatorRegistry, state.Slot) didNotAttestIndices := sliceutil.Not(justifiedAttesterIndices, activeValidatorIndices) for _, index := range didNotAttestIndices { state.ValidatorBalances[index] -= baseReward(state, index, baseRewardQuotient) } return state } // ExpectedFFGTarget applies rewards or penalties // for an expected FFG target. It uses total boundary // attesting balances, total validator balances and base // reward quotient to calculate the reward amount. // Validators who voted for epoch boundary block // will get a reward, everyone else will get a penalty. // // Spec pseudocode definition: // Any validator index in previous_epoch_boundary_attester_indices gains // base_reward(state, index) * previous_epoch_boundary_attesting_balance // total_balance. // Any active validator index not in previous_epoch_boundary_attester_indices loses // base_reward(state, index). func ExpectedFFGTarget( state *pb.BeaconState, boundaryAttesterIndices []uint64, boundaryAttestingBalance uint64, totalBalance uint64) *pb.BeaconState { baseRewardQuotient := baseRewardQuotient(totalBalance) for _, index := range boundaryAttesterIndices { state.ValidatorBalances[index] += baseReward(state, index, baseRewardQuotient) * boundaryAttestingBalance / totalBalance } activeValidatorIndices := validators.ActiveValidatorIndices(state.ValidatorRegistry, state.Slot) didNotAttestIndices := sliceutil.Not(boundaryAttesterIndices, activeValidatorIndices) for _, index := range didNotAttestIndices { state.ValidatorBalances[index] -= baseReward(state, index, baseRewardQuotient) } return state } // ExpectedBeaconChainHead applies rewards or penalties // for an expected beacon chain head. It uses total head // attesting balances, total validator balances and base // reward quotient to calculate the reward amount. // Validators who voted for the canonical head block // will get a reward, everyone else will get a penalty. // // Spec pseudocode definition: // Any validator index in previous_epoch_head_attester_indices gains // base_reward(state, index) * previous_epoch_head_attesting_balance // total_balance). // Any active validator index not in previous_epoch_head_attester_indices loses // base_reward(state, index). func ExpectedBeaconChainHead( state *pb.BeaconState, headAttesterIndices []uint64, headAttestingBalance uint64, totalBalance uint64) *pb.BeaconState { baseRewardQuotient := baseRewardQuotient(totalBalance) for _, index := range headAttesterIndices { state.ValidatorBalances[index] += baseReward(state, index, baseRewardQuotient) * headAttestingBalance / totalBalance } activeValidatorIndices := validators.ActiveValidatorIndices(state.ValidatorRegistry, state.Slot) didNotAttestIndices := sliceutil.Not(headAttesterIndices, activeValidatorIndices) for _, index := range didNotAttestIndices { state.ValidatorBalances[index] -= baseReward(state, index, baseRewardQuotient) } return state } // InclusionDistance applies rewards based on // inclusion distance. It uses calculated inclusion distance // and base reward quotient to calculate the reward amount. // // Spec pseudocode definition: // Any validator index in previous_epoch_attester_indices gains // base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // // inclusion_distance(state, index) func InclusionDistance( state *pb.BeaconState, attesterIndices []uint64, totalBalance uint64) (*pb.BeaconState, error) { baseRewardQuotient := baseRewardQuotient(totalBalance) for _, index := range attesterIndices { inclusionDistance, err := epoch.InclusionDistance(state, index) if err != nil { return nil, fmt.Errorf("could not get inclusion distance: %v", err) } state.ValidatorBalances[index] += baseReward(state, index, baseRewardQuotient) * config.MinAttestationInclusionDelay / inclusionDistance } return state, nil } // InactivityFFGSource applies penalties to inactive // validators that missed to vote FFG source over an // extended of time. (epochs_since_finality > 4) // // Spec pseudocode definition: // Any active validator index not in previous_epoch_justified_attester_indices, // loses inactivity_penalty(state, index, epochs_since_finality) func InactivityFFGSource( state *pb.BeaconState, justifiedAttesterIndices []uint64, totalBalance uint64, epochsSinceFinality uint64) *pb.BeaconState { baseRewardQuotient := baseRewardQuotient(totalBalance) activeValidatorIndices := validators.ActiveValidatorIndices(state.ValidatorRegistry, state.Slot) didNotAttestIndices := sliceutil.Not(justifiedAttesterIndices, activeValidatorIndices) for _, index := range didNotAttestIndices { state.ValidatorBalances[index] -= inactivityPenalty(state, index, baseRewardQuotient, epochsSinceFinality) } return state } // InactivityFFGTarget applies penalties to inactive // validators that missed to vote FFG target over an // extended of time. (epochs_since_finality > 4) // // Spec pseudocode definition: // Any active validator index not in previous_epoch_boundary_attester_indices, // loses inactivity_penalty(state, index, epochs_since_finality) func InactivityFFGTarget( state *pb.BeaconState, boundaryAttesterIndices []uint64, totalBalance uint64, epochsSinceFinality uint64) *pb.BeaconState { baseRewardQuotient := baseRewardQuotient(totalBalance) activeValidatorIndices := validators.ActiveValidatorIndices(state.ValidatorRegistry, state.Slot) didNotAttestIndices := sliceutil.Not(boundaryAttesterIndices, activeValidatorIndices) for _, index := range didNotAttestIndices { state.ValidatorBalances[index] -= inactivityPenalty(state, index, baseRewardQuotient, epochsSinceFinality) } return state } // InactivityChainHead applies penalties to inactive validators // that missed to vote on canonical head over an extended of time. // (epochs_since_finality > 4) // // Spec pseudocode definition: // Any active validator index not in previous_epoch_head_attester_indices, // loses base_reward(state, index) func InactivityChainHead( state *pb.BeaconState, headAttesterIndices []uint64, totalBalance uint64) *pb.BeaconState { baseRewardQuotient := baseRewardQuotient(totalBalance) activeValidatorIndices := validators.ActiveValidatorIndices(state.ValidatorRegistry, state.Slot) didNotAttestIndices := sliceutil.Not(headAttesterIndices, activeValidatorIndices) for _, index := range didNotAttestIndices { state.ValidatorBalances[index] -= baseReward(state, index, baseRewardQuotient) } return state } // InactivityExitedPenalties applies additional (2x) penalties // to inactive validators with status EXITED_WITH_PENALTY. // // Spec pseudocode definition: // Any active_validator index with validator.penalized_slot <= state.slot, // loses 2 * inactivity_penalty(state, index, epochs_since_finality) + // base_reward(state, index). func InactivityExitedPenalties( state *pb.BeaconState, totalBalance uint64, epochsSinceFinality uint64) *pb.BeaconState { baseRewardQuotient := baseRewardQuotient(totalBalance) activeValidatorIndices := validators.ActiveValidatorIndices(state.ValidatorRegistry, state.Slot) for _, index := range activeValidatorIndices { if state.ValidatorRegistry[index].PenalizedSlot <= state.Slot { state.ValidatorBalances[index] -= 2*inactivityPenalty(state, index, baseRewardQuotient, epochsSinceFinality) + baseReward(state, index, baseRewardQuotient) } } return state } // InactivityInclusionDistance applies penalties in relation with // inclusion delay to inactive validators. // // Spec pseudocode definition: // Any validator index in previous_epoch_attester_indices loses // base_reward(state, index) - base_reward(state, index) * // MIN_ATTESTATION_INCLUSION_DELAY // inclusion_distance(state, index) func InactivityInclusionDistance( state *pb.BeaconState, attesterIndices []uint64, totalBalance uint64) (*pb.BeaconState, error) { baseRewardQuotient := baseRewardQuotient(totalBalance) for _, index := range attesterIndices { inclusionDistance, err := epoch.InclusionDistance(state, index) if err != nil { return nil, fmt.Errorf("could not get inclusion distance: %v", err) } baseReward := baseReward(state, index, baseRewardQuotient) state.ValidatorBalances[index] -= baseReward - baseReward*config.MinAttestationInclusionDelay/ inclusionDistance } return state, nil } // AttestationInclusion awards the the beacon // proposers who included previous epoch attestations. // // Spec pseudocode definition: // For each index in previous_epoch_attester_indices, // we determine the proposer proposer_index = // get_beacon_proposer_index(state, inclusion_slot(state, index)) // and set state.validator_balances[proposer_index] += // base_reward(state, index) // INCLUDER_REWARD_QUOTIENT func AttestationInclusion( state *pb.BeaconState, totalBalance uint64, prevEpochAttesterIndices []uint64) (*pb.BeaconState, error) { baseRewardQuotient := baseRewardQuotient(totalBalance) for _, index := range prevEpochAttesterIndices { slot, err := epoch.InclusionSlot(state, index) if err != nil { return nil, fmt.Errorf("could not get inclusion slot: %v", err) } proposerIndex, err := validators.BeaconProposerIdx(state, slot) if err != nil { return nil, fmt.Errorf("could not get propoer index: %v", err) } state.ValidatorBalances[proposerIndex] += baseReward(state, proposerIndex, baseRewardQuotient) / config.IncluderRewardQuotient } return state, nil } // Crosslinks awards or penalizes attesters // for attesting shard cross links. // // Spec pseudocode definition: // For every slot in range(state.slot - 2 * EPOCH_LENGTH, state.slot), // let shard_committee_at_slot = get_shard_committees_at_slot(slot). // For every (shard_committee, shard) in shard_committee_at_slot, compute: // // Let shard_block_root be state.latest_crosslinks[shard].shard_block_root // Let attesting_validator_indices(shard_committee, shard_block_root) // be the union of the validator index sets given by [get_attestation_participants( // state, a.data, a.participation_bitfield) for a in current_epoch_attestations + // previous_epoch_attestations if a.shard == shard and a.shard_block_root == shard_block_root]. // Let winning_root(shard_committee) // be equal to the value of shard_block_root such that sum([get_effective_balance(state, i) // for i in attesting_validator_indices(shard_committee, shard_block_root)]) // is maximized (ties broken by favoring lower shard_block_root values). // Let attesting_validators(shard_committee) // be equal to attesting_validator_indices( // shard_committee, winning_root(shard_committee)) for convenience. // Let total_attesting_balance(shard_committee) = // sum([get_effective_balance(state, i) for i in attesting_validators(shard_committee)]). // Let total_balance(shard_committee) = // sum([get_effective_balance(state, i) for i in shard_committee]). func Crosslinks( state *pb.BeaconState, thisEpochAttestations []*pb.PendingAttestationRecord, prevEpochAttestations []*pb.PendingAttestationRecord) (*pb.BeaconState, error) { epochLength := config.EpochLength startSlot := state.Slot - 2*epochLength for i := startSlot; i < state.Slot; i++ { crosslinkCommittees, err := validators.CrosslinkCommitteesAtSlot(state, i) if err != nil { return nil, fmt.Errorf("could not get shard committees for slot %d: %v", i, err) } for _, crosslinkCommittee := range crosslinkCommittees { shard := crosslinkCommittee.Shard committee := crosslinkCommittee.Committee totalAttestingBalance, err := epoch.TotalAttestingBalance(state, shard, thisEpochAttestations, prevEpochAttestations) if err != nil { return nil, fmt.Errorf("could not get attesting balance for shard committee %d: %v", shard, err) } totalBalance := epoch.TotalBalance(state, committee) baseRewardQuotient := baseRewardQuotient(totalBalance) attestingIndices, err := epoch.AttestingValidators( state, shard, thisEpochAttestations, prevEpochAttestations) if err != nil { return nil, fmt.Errorf("could not get attesting indices for shard committee %d: %v", shard, err) } for _, index := range committee { baseReward := baseReward(state, index, baseRewardQuotient) if sliceutil.IsIn(index, attestingIndices) { state.ValidatorBalances[index] += baseReward * totalAttestingBalance / totalBalance } else { state.ValidatorBalances[index] -= baseReward * totalAttestingBalance / totalBalance } } } } return state, nil } // baseRewardQuotient takes the total balance and calculates for // the quotient of the base reward. // // Spec pseudocode definition: // base_reward_quotient = // integer_squareroot(total_balance) // BASE_REWARD_QUOTIENT func baseRewardQuotient(totalBalance uint64) uint64 { return mathutil.IntegerSquareRoot(totalBalance) / params.BeaconConfig().BaseRewardQuotient } // baseReward takes state and validator index to calculate for // individual validator's base reward. // // Spec pseudocode definition: // base_reward(state, index) = // get_effective_balance(state, index) // base_reward_quotient // 5 func baseReward( state *pb.BeaconState, validatorIndex uint64, baseRewardQuotient uint64) uint64 { validatorBalance := validators.EffectiveBalance(state, validatorIndex) return validatorBalance / baseRewardQuotient / 5 } // inactivityPenalty takes state and validator index to calculate for // individual validator's penalty for being offline. // // Spec pseudocode definition: // inactivity_penalty(state, index, epochs_since_finality) = // base_reward(state, index) + get_effective_balance(state, index) // * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 func inactivityPenalty( state *pb.BeaconState, validatorIndex uint64, baseRewardQuotient uint64, epochsSinceFinality uint64) uint64 { baseReward := baseReward(state, validatorIndex, baseRewardQuotient) validatorBalance := validators.EffectiveBalance(state, validatorIndex) return baseReward + validatorBalance*epochsSinceFinality/config.InactivityPenaltyQuotient/2 }