Skip to content

Commit

Permalink
Ensure balance unchanged for optimal validators (#5946)
Browse files Browse the repository at this point in the history
  • Loading branch information
terencechain committed May 21, 2020
1 parent f093bc1 commit a56666c
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 16 deletions.
65 changes: 50 additions & 15 deletions beacon-chain/core/epoch/precompute/reward_penalty.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package precompute

import (
"github.com/pkg/errors"

"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/shared/mathutil"
Expand Down Expand Up @@ -78,44 +77,62 @@ func attestationDelta(state *stateTrie.BeaconState, pBal *Balance, v *Validator)

baseRewardsPerEpoch := params.BeaconConfig().BaseRewardsPerEpoch
effectiveBalanceIncrement := params.BeaconConfig().EffectiveBalanceIncrement
e := helpers.PrevEpoch(state)
vb := v.CurrentEpochEffectiveBalance
br := vb * params.BeaconConfig().BaseRewardFactor / mathutil.IntegerSquareRoot(pBal.ActiveCurrentEpoch) / baseRewardsPerEpoch
r, p := uint64(0), uint64(0)

// Process source reward / penalty
if v.IsPrevEpochAttester && !v.IsSlashed {
rewardNumerator := br * (pBal.PrevEpochAttested / effectiveBalanceIncrement)
r += rewardNumerator / (pBal.ActiveCurrentEpoch / effectiveBalanceIncrement)
proposerReward := br / params.BeaconConfig().ProposerRewardQuotient
maxAttesterReward := br - proposerReward
r += maxAttesterReward / v.InclusionDistance
if isInInactivityLeak(state) {
// Since full base reward will be canceled out by inactivity penalty deltas,
// optimal participation receives full base reward compensation here.
r += br
} else {
rewardNumerator := br * (pBal.PrevEpochAttested / effectiveBalanceIncrement)
r += rewardNumerator / (pBal.ActiveCurrentEpoch / effectiveBalanceIncrement)
proposerReward := br / params.BeaconConfig().ProposerRewardQuotient
maxAttesterReward := br - proposerReward
r += maxAttesterReward / v.InclusionDistance
}
} else {
p += br
}

// Process target reward / penalty
if v.IsPrevEpochTargetAttester && !v.IsSlashed {
rewardNumerator := br * (pBal.PrevEpochTargetAttested / effectiveBalanceIncrement)
r += rewardNumerator / (pBal.ActiveCurrentEpoch / effectiveBalanceIncrement)
if isInInactivityLeak(state) {
// Since full base reward will be canceled out by inactivity penalty deltas,
// optimal participation receives full base reward compensation here.
r += br
} else {
rewardNumerator := br * (pBal.PrevEpochTargetAttested / effectiveBalanceIncrement)
r += rewardNumerator / (pBal.ActiveCurrentEpoch / effectiveBalanceIncrement)
}
} else {
p += br
}

// Process head reward / penalty
if v.IsPrevEpochHeadAttester && !v.IsSlashed {
rewardNumerator := br * (pBal.PrevEpochHeadAttested / effectiveBalanceIncrement)
r += rewardNumerator / (pBal.ActiveCurrentEpoch / effectiveBalanceIncrement)
if isInInactivityLeak(state) {
// Since full base reward will be canceled out by inactivity penalty deltas,
// optimal participation receives full base reward compensation here.
r += br
} else {
rewardNumerator := br * (pBal.PrevEpochHeadAttested / effectiveBalanceIncrement)
r += rewardNumerator / (pBal.ActiveCurrentEpoch / effectiveBalanceIncrement)
}
} else {
p += br
}

// Process finality delay penalty
finalizedEpoch := state.FinalizedCheckpointEpoch()
finalityDelay := e - finalizedEpoch
finalityDelay := finalityDelay(state)

if finalityDelay > params.BeaconConfig().MinEpochsToInactivityPenalty {
p += baseRewardsPerEpoch * br
if isInInactivityLeak(state) {
// If validator is performing optimally, this cancels all rewards for a neutral balance.
proposerReward := br / params.BeaconConfig().ProposerRewardQuotient
p += baseRewardsPerEpoch*br - proposerReward
// Apply an additional penalty to validators that did not vote on the correct target or has been slashed.
// Equivalent to the following condition from the spec:
// `index not in get_unslashed_attesting_indices(state, matching_target_attestations)`
Expand Down Expand Up @@ -154,3 +171,21 @@ func proposerDeltaPrecompute(state *stateTrie.BeaconState, pBal *Balance, vp []*
}
return rewards, nil
}

// isInInactivityLeak returns true if the state is experiencing inactivity leak.
//
// Spec code:
// def is_in_inactivity_leak(state: BeaconState) -> bool:
// return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY
func isInInactivityLeak(state *stateTrie.BeaconState) bool {
return finalityDelay(state) > params.BeaconConfig().MinEpochsToInactivityPenalty
}

// finalityDelay returns the finality delay using the beacon state.
//
// Spec code:
// def get_finality_delay(state: BeaconState) -> uint64:
// return get_previous_epoch(state) - state.finalized_checkpoint.epoch
func finalityDelay(state *stateTrie.BeaconState) uint64 {
return helpers.PrevEpoch(state) - state.FinalizedCheckpointEpoch()
}
61 changes: 60 additions & 1 deletion beacon-chain/core/epoch/precompute/reward_penalty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ func TestProcessRewardsAndPenaltiesPrecompute_SlashedInactivePenalty(t *testing.
t.Errorf("Could not get base reward: %v", err)
}
penalty := 3 * base
penalty += params.BeaconConfig().BaseRewardsPerEpoch * base
proposerReward := base / params.BeaconConfig().ProposerRewardQuotient
penalty += params.BeaconConfig().BaseRewardsPerEpoch*base - proposerReward
penalty += vp[i].CurrentEpochEffectiveBalance * finalityDelay / params.BeaconConfig().InactivityPenaltyQuotient
if penalties[i] != penalty {
t.Errorf("Wanted slashed indices penalty balance %d, got %d", penalty, penalties[i])
Expand Down Expand Up @@ -381,3 +382,61 @@ func TestProposerDeltaPrecompute_SlashedCase(t *testing.T) {
t.Errorf("Wanted proposer reward for slashed %d, got %d", 0, r[proposerIndex])
}
}

func TestFinalityDelay(t *testing.T) {
base := buildState(params.BeaconConfig().SlotsPerEpoch*10, 1)
base.FinalizedCheckpoint = &ethpb.Checkpoint{Epoch: 3}
state, err := state.InitializeFromProto(base)
if err != nil {
t.Fatal(err)
}
d := finalityDelay(state)
w := helpers.PrevEpoch(state) - state.FinalizedCheckpointEpoch()
if d != w {
t.Error("Did not get wanted finality delay")
}

if err := state.SetFinalizedCheckpoint(&ethpb.Checkpoint{Epoch: 4}); err != nil {
t.Fatal(err)
}
d = finalityDelay(state)
w = helpers.PrevEpoch(state) - state.FinalizedCheckpointEpoch()
if d != w {
t.Error("Did not get wanted finality delay")
}

if err := state.SetFinalizedCheckpoint(&ethpb.Checkpoint{Epoch: 5}); err != nil {
t.Fatal(err)
}
d = finalityDelay(state)
w = helpers.PrevEpoch(state) - state.FinalizedCheckpointEpoch()
if d != w {
t.Error("Did not get wanted finality delay")
}
}

func TestIsInInactivityLeak(t *testing.T) {
base := buildState(params.BeaconConfig().SlotsPerEpoch*10, 1)
base.FinalizedCheckpoint = &ethpb.Checkpoint{Epoch: 3}
state, err := state.InitializeFromProto(base)
if err != nil {
t.Fatal(err)
}
if !isInInactivityLeak(state) {
t.Error("Wanted inactivity leak true")
}

if err := state.SetFinalizedCheckpoint(&ethpb.Checkpoint{Epoch: 4}); err != nil {
t.Fatal(err)
}
if !isInInactivityLeak(state) {
t.Error("Wanted inactivity leak true")
}

if err := state.SetFinalizedCheckpoint(&ethpb.Checkpoint{Epoch: 5}); err != nil {
t.Fatal(err)
}
if isInInactivityLeak(state) {
t.Error("Wanted inactivity leak false")
}
}

0 comments on commit a56666c

Please sign in to comment.