mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-23 20:07:17 +00:00
197 lines
7.1 KiB
Go
197 lines
7.1 KiB
Go
// Package incentives defines Casper Proof of Stake rewards and penalties for validator
|
|
// records based on Vitalik Buterin's Friendly Finality Gadget protocol. Validator balances
|
|
// depend on time to finality as well as deposit-weighted functions. This package provides
|
|
// pure functions that can then be incorporated into a beacon chain state transition.
|
|
package incentives
|
|
|
|
import (
|
|
v "github.com/prysmaticlabs/prysm/beacon-chain/core/validators"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/utils"
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
|
"github.com/prysmaticlabs/prysm/shared/bitutil"
|
|
"github.com/prysmaticlabs/prysm/shared/mathutil"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
)
|
|
|
|
// TallyVoteBalances calculates all the votes behind a block and
|
|
// then rewards validators for their participation in voting for that block.
|
|
func TallyVoteBalances(
|
|
blockHash [32]byte,
|
|
blockVoteCache utils.BlockVoteCache,
|
|
validators []*pb.ValidatorRecord,
|
|
activeValidatorIndices []uint32,
|
|
totalActiveValidatorDeposit uint64,
|
|
timeSinceFinality uint64,
|
|
) (uint64, []*pb.ValidatorRecord) {
|
|
blockVote, ok := blockVoteCache[blockHash]
|
|
if !ok {
|
|
return 0, validators
|
|
}
|
|
|
|
blockVoteBalance := blockVote.VoteTotalDeposit
|
|
voterIndices := blockVote.VoterIndices
|
|
newValidatorRegistry := CalculateRewards(
|
|
voterIndices,
|
|
activeValidatorIndices,
|
|
validators,
|
|
totalActiveValidatorDeposit,
|
|
blockVoteBalance,
|
|
timeSinceFinality,
|
|
)
|
|
|
|
return blockVoteBalance, newValidatorRegistry
|
|
}
|
|
|
|
// CalculateRewards adjusts validators balances by applying rewards or penalties
|
|
// based on FFG incentive structure.
|
|
// FFG Rewards scheme rewards validator who have voted on blocks, and penalises those validators
|
|
// who are offline. The penalties are more severe the longer they are offline.
|
|
func CalculateRewards(
|
|
voterIndices []uint32,
|
|
activeValidatorIndices []uint32,
|
|
validators []*pb.ValidatorRecord,
|
|
totalActiveValidatorDeposit uint64,
|
|
totalParticipatedDeposit uint64,
|
|
timeSinceFinality uint64,
|
|
) []*pb.ValidatorRecord {
|
|
|
|
newValidatorSet := v.CopyValidatorRegistry(validators)
|
|
|
|
// Calculate the reward and penalty quotients for the validator set.
|
|
rewardQuotient := RewardQuotient(totalActiveValidatorDeposit)
|
|
penaltyQuotient := QuadraticPenaltyQuotient()
|
|
|
|
if timeSinceFinality <= 3*params.BeaconConfig().CycleLength {
|
|
for _, validatorIndex := range activeValidatorIndices {
|
|
var voted bool
|
|
|
|
for _, voterIndex := range voterIndices {
|
|
if voterIndex == validatorIndex {
|
|
voted = true
|
|
balance := validators[validatorIndex].GetBalance()
|
|
newBalance := int64(balance) + int64(balance/rewardQuotient)*(2*int64(totalParticipatedDeposit)-int64(totalActiveValidatorDeposit))/int64(totalActiveValidatorDeposit)
|
|
newValidatorSet[validatorIndex].Balance = uint64(newBalance)
|
|
break
|
|
}
|
|
}
|
|
|
|
if !voted {
|
|
newBalance := newValidatorSet[validatorIndex].GetBalance()
|
|
newBalance -= newBalance / rewardQuotient
|
|
newValidatorSet[validatorIndex].Balance = newBalance
|
|
}
|
|
}
|
|
|
|
} else {
|
|
for _, validatorIndex := range activeValidatorIndices {
|
|
var voted bool
|
|
|
|
for _, voterIndex := range voterIndices {
|
|
if voterIndex == validatorIndex {
|
|
voted = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !voted {
|
|
newBalance := newValidatorSet[validatorIndex].GetBalance()
|
|
newBalance -= newBalance/rewardQuotient + newBalance*timeSinceFinality/penaltyQuotient
|
|
newValidatorSet[validatorIndex].Balance = newBalance
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return newValidatorSet
|
|
}
|
|
|
|
// ApplyCrosslinkRewardsAndPenalties applies the appropriate rewards and
|
|
// penalties according to the attestation for a shard.
|
|
func ApplyCrosslinkRewardsAndPenalties(
|
|
crosslinkRecords []*pb.CrosslinkRecord,
|
|
slot uint64,
|
|
attesterIndices []uint32,
|
|
attestation *pb.AggregatedAttestation,
|
|
validators []*pb.ValidatorRecord,
|
|
totalActiveValidatorDeposit uint64,
|
|
totalBalance uint64,
|
|
voteBalance uint64,
|
|
) ([]*pb.ValidatorRecord, error) {
|
|
newValidatorSet := v.CopyValidatorRegistry(validators)
|
|
|
|
rewardQuotient := RewardQuotient(totalActiveValidatorDeposit)
|
|
for _, attesterIndex := range attesterIndices {
|
|
timeSinceLastConfirmation := slot - crosslinkRecords[attestation.Shard].GetSlot()
|
|
|
|
checkBit, err := bitutil.CheckBit(attestation.AttesterBitfield, int(attesterIndex))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if checkBit {
|
|
newValidatorSet[attesterIndex] = RewardValidatorCrosslink(totalBalance, voteBalance, rewardQuotient, newValidatorSet[attesterIndex])
|
|
} else {
|
|
newValidatorSet[attesterIndex] = PenaliseValidatorCrosslink(timeSinceLastConfirmation, rewardQuotient, newValidatorSet[attesterIndex])
|
|
}
|
|
}
|
|
return newValidatorSet, nil
|
|
}
|
|
|
|
// RewardQuotient returns the reward quotient for validators which will be used to
|
|
// reward validators for voting on blocks, or penalise them for being offline.
|
|
func RewardQuotient(totalActiveValidatorDeposit uint64) uint64 {
|
|
totalDepositETH := totalActiveValidatorDeposit / params.BeaconConfig().Gwei
|
|
return params.BeaconConfig().BaseRewardQuotient * mathutil.IntegerSquareRoot(totalDepositETH)
|
|
}
|
|
|
|
// QuadraticPenaltyQuotient is the quotient that will be used to apply penalties to offline
|
|
// validators.
|
|
func QuadraticPenaltyQuotient() uint64 {
|
|
dropTimeFactor := params.BeaconConfig().SqrtExpDropTime
|
|
return dropTimeFactor * dropTimeFactor
|
|
}
|
|
|
|
// QuadraticPenalty returns the penalty that will be applied to an offline validator
|
|
// based on the number of slots that they are offline.
|
|
func QuadraticPenalty(numberOfSlots uint64) uint64 {
|
|
slotFactor := (numberOfSlots * numberOfSlots) / 2
|
|
penaltyQuotient := QuadraticPenaltyQuotient()
|
|
return slotFactor / penaltyQuotient
|
|
}
|
|
|
|
// RewardValidatorCrosslink applies rewards to validators part of a shard committee for voting on a shard.
|
|
// TODO(#538): Change this to big.Int as tests using 64 bit integers fail due to integer overflow.
|
|
func RewardValidatorCrosslink(
|
|
totalDeposit uint64,
|
|
participatedDeposits uint64,
|
|
rewardQuotient uint64,
|
|
validator *pb.ValidatorRecord,
|
|
) *pb.ValidatorRecord {
|
|
currentBalance := int64(validator.Balance)
|
|
currentBalance += int64(currentBalance) / int64(rewardQuotient) * (2*int64(participatedDeposits) - int64(totalDeposit)) / int64(totalDeposit)
|
|
return &pb.ValidatorRecord{
|
|
Pubkey: validator.Pubkey,
|
|
RandaoCommitmentHash32: validator.RandaoCommitmentHash32,
|
|
Balance: uint64(currentBalance),
|
|
Status: validator.Status,
|
|
LatestStatusChangeSlot: validator.LatestStatusChangeSlot,
|
|
}
|
|
}
|
|
|
|
// PenaliseValidatorCrosslink applies penalties to validators part of a shard committee for not voting on a shard.
|
|
func PenaliseValidatorCrosslink(
|
|
timeSinceLastConfirmation uint64,
|
|
rewardQuotient uint64,
|
|
validator *pb.ValidatorRecord,
|
|
) *pb.ValidatorRecord {
|
|
newBalance := validator.Balance
|
|
quadraticQuotient := QuadraticPenaltyQuotient()
|
|
newBalance -= newBalance/rewardQuotient + newBalance*timeSinceLastConfirmation/quadraticQuotient
|
|
return &pb.ValidatorRecord{
|
|
Pubkey: validator.Pubkey,
|
|
RandaoCommitmentHash32: validator.RandaoCommitmentHash32,
|
|
Balance: uint64(newBalance),
|
|
Status: validator.Status,
|
|
LatestStatusChangeSlot: validator.LatestStatusChangeSlot,
|
|
}
|
|
}
|