Skip to content

Commit

Permalink
Added reward/penalties processing (#6819)
Browse files Browse the repository at this point in the history
  • Loading branch information
Giulio2002 authored Feb 9, 2023
1 parent 69e8ac0 commit 64ddd9f
Show file tree
Hide file tree
Showing 15 changed files with 231 additions and 36 deletions.
8 changes: 8 additions & 0 deletions cl/clparams/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,14 @@ func configForkNames(b *BeaconChainConfig) map[[VersionLength]byte]string {
return fvn
}

func (b *BeaconChainConfig) ParticipationWeights() []uint64 {
return []uint64{
b.TimelySourceWeight,
b.TimelyTargetWeight,
b.TimelyHeadWeight,
}
}

var MainnetBeaconConfig BeaconChainConfig = BeaconChainConfig{
// Constants (Non-configurable)
FarFutureEpoch: math.MaxUint64,
Expand Down
14 changes: 14 additions & 0 deletions cl/utils/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ func DecodeSSZSnappy(dst ssz_utils.Unmarshaler, src []byte) error {
return nil
}

func DecodeSSZSnappyWithVersion(dst ssz_utils.Unmarshaler, src []byte, version int) error {
dec, err := snappy.Decode(nil, src)
if err != nil {
return err
}

err = dst.DecodeSSZWithVersion(dec, version)
if err != nil {
return err
}

return nil
}

func CompressZstd(b []byte) []byte {
wr, err := zstd.NewWriter(nil)
if err != nil {
Expand Down
136 changes: 132 additions & 4 deletions cmd/erigon-cl/core/state/accessors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
eth2_shuffle "github.com/protolambda/eth2-shuffle"
)

const PreAllocatedRewardsAndPenalties = 8192

// GetActiveValidatorsIndices returns the list of validator indices active for the given epoch.
func (b *BeaconState) GetActiveValidatorsIndices(epoch uint64) (indicies []uint64) {
if cachedIndicies, ok := b.activeValidatorsCache.Get(epoch); ok {
Expand Down Expand Up @@ -92,11 +94,11 @@ func (b *BeaconState) GetTotalBalance(validatorSet []uint64) (uint64, error) {
}

// GetTotalActiveBalance return the sum of all balances within active validators.
func (b *BeaconState) GetTotalActiveBalance() (uint64, error) {
func (b *BeaconState) GetTotalActiveBalance() uint64 {
if b.totalActiveBalanceCache < b.beaconConfig.EffectiveBalanceIncrement {
return b.beaconConfig.EffectiveBalanceIncrement, nil
return b.beaconConfig.EffectiveBalanceIncrement
}
return b.totalActiveBalanceCache, nil
return b.totalActiveBalanceCache
}

// GetTotalSlashingAmount return the sum of all slashings.
Expand Down Expand Up @@ -267,7 +269,7 @@ func (b *BeaconState) BaseReward(totalActiveBalance, index uint64) (uint64, erro

// SyncRewards returns the proposer reward and the sync participant reward given the total active balance in state.
func (b *BeaconState) SyncRewards() (proposerReward, participantReward uint64, err error) {
activeBalance, err := b.GetTotalActiveBalance()
activeBalance := b.GetTotalActiveBalance()
if err != nil {
return 0, 0, err
}
Expand Down Expand Up @@ -400,3 +402,129 @@ func (b *BeaconState) GetAttestingIndicies(attestation *cltypes.AttestationData,
}
return attestingIndices, nil
}

// Implementation of get_eligible_validator_indices as defined in the eth 2.0 specs.
func (b *BeaconState) EligibleValidatorsIndicies() (eligibleValidators []uint64) {
eligibleValidators = make([]uint64, 0, len(b.validators))
previousEpoch := b.PreviousEpoch()
// TODO(Giulio2002): Proper caching
for i, validator := range b.validators {
if validator.Active(previousEpoch) || (validator.Slashed && previousEpoch+1 < validator.WithdrawableEpoch) {
eligibleValidators = append(eligibleValidators, uint64(i))
}
}
return
}

// Implementation of is_in_inactivity_leak. tells us if network is in danger pretty much. defined in ETH 2.0 specs.
func (b *BeaconState) inactivityLeaking() bool {
return (b.PreviousEpoch() - b.finalizedCheckpoint.Epoch) > b.beaconConfig.MinEpochsToInactivityPenalty
}

// Implementation defined in ETH 2.0 specs: https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#get_flag_index_deltas.
// Although giulio made it efficient hopefully. results will be written in the input map.
func (b *BeaconState) processFlagIndexDeltas(flagIdx int, balanceDeltaMap map[uint64]int64, eligibleValidators []uint64) (err error) {
// Initialize variables
var (
unslashedParticipatingIndicies []uint64
unslashedParticipatingTotalBalance uint64
baseReward uint64
)
// Find unslashedParticipatingIndicies for this specific flag index.
previousEpoch := b.PreviousEpoch()
unslashedParticipatingIndicies, err = b.GetUnslashedParticipatingIndices(flagIdx, previousEpoch)
if err != nil {
return
}
// Find current weight for flag index.
weights := b.beaconConfig.ParticipationWeights()
weight := weights[flagIdx]
// Compute participating indices total balance (required in rewards/penalties computation).
unslashedParticipatingTotalBalance, err = b.GetTotalBalance(unslashedParticipatingIndicies)
if err != nil {
return
}
// Make it a map to make the existence check O(1) time-complexity.
isUnslashedParticipatingIndicies := make(map[uint64]struct{})
for _, index := range unslashedParticipatingIndicies {
isUnslashedParticipatingIndicies[index] = struct{}{}
}
// Compute relative increments.
unslashedParticipatingIncrements := unslashedParticipatingTotalBalance / b.beaconConfig.EffectiveBalanceIncrement
activeIncrements := b.GetTotalActiveBalance() / b.beaconConfig.EffectiveBalanceIncrement
totalActiveBalance := b.GetTotalActiveBalance()
// Now process deltas and whats nots.
for _, index := range eligibleValidators {
if _, ok := isUnslashedParticipatingIndicies[index]; ok {
if b.inactivityLeaking() {
continue
}
rewardNumerator := baseReward * weight * unslashedParticipatingIncrements
balanceDeltaMap[index] += int64(rewardNumerator / (activeIncrements * b.beaconConfig.WeightDenominator))
} else if flagIdx != int(b.beaconConfig.TimelyHeadFlagIndex) {
baseReward, err = b.BaseReward(totalActiveBalance, index)
if err != nil {
return
}
balanceDeltaMap[index] -= int64(baseReward * weight / b.beaconConfig.WeightDenominator)
}
}
return
}

// Implemention defined in https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#modified-get_inactivity_penalty_deltas.
func (b *BeaconState) processInactivityDeltas(balanceDeltaMap map[uint64]int64, eligibleValidators []uint64) (err error) {
var (
unslashedParticipatingIndicies []uint64
validator cltypes.Validator
)
// Find unslashedParticipatingIndicies for this specific flag index.
previousEpoch := b.PreviousEpoch()
unslashedParticipatingIndicies, err = b.GetUnslashedParticipatingIndices(int(b.beaconConfig.TimelyHeadFlagIndex), previousEpoch)
if err != nil {
return
}
// Make it a map to make the existence check O(1) time-complexity.
isUnslashedParticipatingIndicies := make(map[uint64]struct{})
for _, index := range unslashedParticipatingIndicies {
isUnslashedParticipatingIndicies[index] = struct{}{}
}
// retrieve penalty quotient based on fork
var penaltyQuotient uint64
switch b.version {
case clparams.Phase0Version:
penaltyQuotient = b.beaconConfig.InactivityPenaltyQuotient
case clparams.AltairVersion:
penaltyQuotient = b.beaconConfig.InactivityPenaltyQuotientAltair
case clparams.BellatrixVersion:
penaltyQuotient = b.beaconConfig.InactivityPenaltyQuotientBellatrix
}
for _, index := range eligibleValidators {
if _, ok := isUnslashedParticipatingIndicies[index]; ok {
continue
}
// Process inactivity penalties.
validator, err = b.ValidatorAt(int(index))
if err != nil {
return err
}
penaltyNumerator := validator.EffectiveBalance * b.inactivityScores[index]
balanceDeltaMap[index] -= int64(penaltyNumerator / (b.beaconConfig.InactivityScoreBias * penaltyQuotient))
}
return
}

// BalanceDeltas return the delta for each validator index.
func (b *BeaconState) BalanceDeltas() (balanceDeltaMap map[uint64]int64, err error) {
balanceDeltaMap = map[uint64]int64{}
eligibleValidators := b.EligibleValidatorsIndicies()
// process each flag indexes by weight.
for i := range b.beaconConfig.ParticipationWeights() {
if err = b.processFlagIndexDeltas(i, balanceDeltaMap, eligibleValidators); err != nil {
return
}
}
// process inactivity scores now.
err = b.processInactivityDeltas(balanceDeltaMap, eligibleValidators)
return
}
4 changes: 1 addition & 3 deletions cmd/erigon-cl/core/state/accessors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ func TestActiveValidatorIndices(t *testing.T) {
require.NoError(t, err)
require.Equal(t, set, []uint64{1})
// Check if balances are retrieved correctly
totalBalance, err := testState.GetTotalActiveBalance()
require.NoError(t, err)
require.Equal(t, totalBalance, uint64(2e9))
require.Equal(t, testState.GetTotalActiveBalance(), uint64(2e9))
}

func TestGetBlockRoot(t *testing.T) {
Expand Down
10 changes: 5 additions & 5 deletions cmd/erigon-cl/core/state/mutators.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"fmt"
)

func (b *BeaconState) IncreaseBalance(index int, delta uint64) error {
currentBalance, err := b.ValidatorBalance(index)
func (b *BeaconState) IncreaseBalance(index, delta uint64) error {
currentBalance, err := b.ValidatorBalance(int(index))
if err != nil {
return err
}
return b.SetValidatorBalance(index, currentBalance+delta)
return b.SetValidatorBalance(int(index), currentBalance+delta)
}

func (b *BeaconState) DecreaseBalance(index, delta uint64) error {
Expand Down Expand Up @@ -101,8 +101,8 @@ func (b *BeaconState) SlashValidator(slashedInd, whistleblowerInd uint64) error
}
whistleBlowerReward := newValidator.EffectiveBalance / b.beaconConfig.WhistleBlowerRewardQuotient
proposerReward := whistleBlowerReward / b.beaconConfig.ProposerRewardQuotient
if err := b.IncreaseBalance(int(proposerInd), proposerReward); err != nil {
if err := b.IncreaseBalance(proposerInd, proposerReward); err != nil {
return err
}
return b.IncreaseBalance(int(whistleblowerInd), whistleBlowerReward)
return b.IncreaseBalance(whistleblowerInd, whistleBlowerReward)
}
2 changes: 1 addition & 1 deletion cmd/erigon-cl/core/state/mutators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestIncreaseBalance(t *testing.T) {
testInd := uint64(42)
amount := uint64(100)
beforeBalance := state.Balances()[testInd]
state.IncreaseBalance(int(testInd), amount)
state.IncreaseBalance(testInd, amount)
afterBalance := state.Balances()[testInd]
require.Equal(t, afterBalance, beforeBalance+amount)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package transition

import (
"fmt"

"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes"
)
Expand Down Expand Up @@ -87,11 +85,10 @@ func (s *StateTransistor) processJustificationBitsAndFinalityAltair() error {
if err != nil {
return err
}
totalActiveBalance, err := s.state.GetTotalActiveBalance()
totalActiveBalance := s.state.GetTotalActiveBalance()
if err != nil {
return err
}
fmt.Println(totalActiveBalance > 1000000000)
previousTargetBalance, err := s.state.GetTotalBalance(previousIndices)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion cmd/erigon-cl/core/transition/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func (s *StateTransistor) ProcessDeposit(deposit *cltypes.Deposit) error {
return nil
}
// Increase the balance if exists already
return s.state.IncreaseBalance(int(validatorIndex), amount)
return s.state.IncreaseBalance(validatorIndex, amount)

}

Expand Down
15 changes: 3 additions & 12 deletions cmd/erigon-cl/core/transition/process_attestations.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,7 @@ func (s *StateTransistor) ProcessAttestations(attestations []*cltypes.Attestatio

// ProcessAttestation takes an attestation and process it.
func (s *StateTransistor) processAttestation(attestation *cltypes.Attestation) ([]uint64, error) {
participationFlagWeights := []uint64{
s.beaconConfig.TimelySourceWeight,
s.beaconConfig.TimelyTargetWeight,
s.beaconConfig.TimelyHeadWeight,
}

totalActiveBalance, err := s.state.GetTotalActiveBalance()
if err != nil {
return nil, err
}
totalActiveBalance := s.state.GetTotalActiveBalance()
data := attestation.Data
currentEpoch := s.state.Epoch()
previousEpoch := s.state.PreviousEpoch()
Expand Down Expand Up @@ -70,7 +61,7 @@ func (s *StateTransistor) processAttestation(attestation *cltypes.Attestation) (
}

for _, attesterIndex := range attestingIndicies {
for flagIndex, weight := range participationFlagWeights {
for flagIndex, weight := range s.beaconConfig.ParticipationWeights() {
if !slices.Contains(participationFlagsIndicies, uint8(flagIndex)) || epochParticipation[attesterIndex].HasFlag(flagIndex) {
continue
}
Expand All @@ -95,7 +86,7 @@ func (s *StateTransistor) processAttestation(attestation *cltypes.Attestation) (
}
proposerRewardDenominator := (s.beaconConfig.WeightDenominator - s.beaconConfig.ProposerWeight) * s.beaconConfig.WeightDenominator / s.beaconConfig.ProposerWeight
reward := proposerRewardNumerator / proposerRewardDenominator
return attestingIndicies, s.state.IncreaseBalance(int(proposer), reward)
return attestingIndicies, s.state.IncreaseBalance(proposer, reward)
}

type verifyAttestationWorkersResult struct {
Expand Down
25 changes: 25 additions & 0 deletions cmd/erigon-cl/core/transition/process_rewards_and_penalties.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package transition

// ProcessRewardsAndPenalties applies rewards/penalties accumulated during previous epoch.
func (s *StateTransistor) ProcessRewardsAndPenalties() error {
// Get deltas for epoch transition.
deltas, err := s.state.BalanceDeltas()
if err != nil {
return err
}
// Apply deltas
for index, delta := range deltas {
if delta > 0 {
// Increment
if err := s.state.IncreaseBalance(index, uint64(delta)); err != nil {
return err
}
continue
}
// Decrease balance
if err := s.state.DecreaseBalance(index, uint64(-delta)); err != nil {
return err
}
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package transition_test

import (
_ "embed"
"testing"

"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/utils"
"github.com/ledgerwatch/erigon/cmd/erigon-cl/core/state"
"github.com/ledgerwatch/erigon/cmd/erigon-cl/core/transition"
"github.com/stretchr/testify/require"
)

//go:embed test_data/rewards_finality_test_expected.ssz_snappy
var expectedState []byte

//go:embed test_data/rewards_finality_test_state.ssz_snappy
var startingState []byte

func TestProcessRewardsAndPenalties(t *testing.T) {
// Load test states.
testState := state.New(&clparams.MainnetBeaconConfig)
require.NoError(t, utils.DecodeSSZSnappyWithVersion(testState, startingState, int(clparams.BellatrixVersion)))
expected := state.New(&clparams.MainnetBeaconConfig)
require.NoError(t, utils.DecodeSSZSnappyWithVersion(expected, expectedState, int(clparams.BellatrixVersion)))
// Make up state transistor
s := transition.New(testState, &clparams.MainnetBeaconConfig, nil, false)
// Do processing
require.NoError(t, s.ProcessRewardsAndPenalties())
// Now compare if the two states are the same by taking their root and comparing.
haveRoot, err := testState.HashSSZ()
require.NoError(t, err)
expectedRoot, err := testState.HashSSZ()
require.NoError(t, err)
// Lastly compare
require.Equal(t, expectedRoot, haveRoot)
}
5 changes: 1 addition & 4 deletions cmd/erigon-cl/core/transition/process_slashings.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ func (s *StateTransistor) processSlashings(slashingMultiplier uint64) error {
// Get the current epoch
epoch := s.state.Epoch()
// Get the total active balance
totalBalance, err := s.state.GetTotalActiveBalance()
if err != nil {
return err
}
totalBalance := s.state.GetTotalActiveBalance()
// Calculate the total slashing amount
// by summing all slashings and multiplying by the provided multiplier
slashing := s.state.GetTotalSlashingAmount() * slashingMultiplier
Expand Down
Loading

0 comments on commit 64ddd9f

Please sign in to comment.