diff --git a/beacon-chain/core/altair/attestation.go b/beacon-chain/core/altair/attestation.go index 12a01ffba3..6f17d0aad9 100644 --- a/beacon-chain/core/altair/attestation.go +++ b/beacon-chain/core/altair/attestation.go @@ -2,6 +2,7 @@ package altair import ( "bytes" + "fmt" "github.com/pkg/errors" types "github.com/prysmaticlabs/eth2-types" @@ -22,6 +23,68 @@ func AddValidatorFlag(flag, flagPosition uint8) uint8 { return flag | (1 << flagPosition) } +// EpochParticipation sets and returns the proposer reward numerator and epoch participation. +// +// Spec code: +// proposer_reward_numerator = 0 +// for index in get_attesting_indices(state, data, attestation.aggregation_bits): +// for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): +// if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): +// epoch_participation[index] = add_flag(epoch_participation[index], flag_index) +// proposer_reward_numerator += get_base_reward(state, index) * weight +func EpochParticipation(beaconState state.BeaconState, indices []uint64, epochParticipation []byte, participatedFlags map[uint8]bool) (uint64, []byte, error) { + cfg := params.BeaconConfig() + sourceFlagIndex := cfg.TimelySourceFlagIndex + targetFlagIndex := cfg.TimelyTargetFlagIndex + headFlagIndex := cfg.TimelyHeadFlagIndex + proposerRewardNumerator := uint64(0) + totalBalance, err := helpers.TotalActiveBalance(beaconState) + if err != nil { + return 0, nil, err + } + for _, index := range indices { + if index >= uint64(len(epochParticipation)) { + return 0, nil, fmt.Errorf("index %d exceeds participation length %d", index, len(epochParticipation)) + } + br, err := BaseRewardWithTotalBalance(beaconState, types.ValidatorIndex(index), totalBalance) + if err != nil { + return 0, nil, err + } + if participatedFlags[sourceFlagIndex] && !HasValidatorFlag(epochParticipation[index], sourceFlagIndex) { + epochParticipation[index] = AddValidatorFlag(epochParticipation[index], sourceFlagIndex) + proposerRewardNumerator += br * cfg.TimelySourceWeight + } + if participatedFlags[targetFlagIndex] && !HasValidatorFlag(epochParticipation[index], targetFlagIndex) { + epochParticipation[index] = AddValidatorFlag(epochParticipation[index], targetFlagIndex) + proposerRewardNumerator += br * cfg.TimelyTargetWeight + } + if participatedFlags[headFlagIndex] && !HasValidatorFlag(epochParticipation[index], headFlagIndex) { + epochParticipation[index] = AddValidatorFlag(epochParticipation[index], headFlagIndex) + proposerRewardNumerator += br * cfg.TimelyHeadWeight + } + } + + return proposerRewardNumerator, epochParticipation, nil +} + +// RewardProposer rewards proposer by increasing proposer's balance with input reward numerator and calculated reward denominator. +// +// Spec code: +// proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT +// proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) +// increase_balance(state, get_beacon_proposer_index(state), proposer_reward) +func RewardProposer(beaconState state.BeaconState, proposerRewardNumerator uint64) error { + cfg := params.BeaconConfig() + d := (cfg.WeightDenominator - cfg.ProposerWeight) * cfg.WeightDenominator / cfg.ProposerWeight + proposerReward := proposerRewardNumerator / d + i, err := helpers.BeaconProposerIndex(beaconState) + if err != nil { + return err + } + + return helpers.IncreaseBalance(beaconState, i, proposerReward) +} + // AttestationParticipationFlagIndices retrieves a map of attestation scoring based on Altair's participation flag indices. // This is used to facilitate process attestation during state transition and during upgrade to altair state. // diff --git a/beacon-chain/core/altair/attestation_test.go b/beacon-chain/core/altair/attestation_test.go index 14a06c8086..4030638f7c 100644 --- a/beacon-chain/core/altair/attestation_test.go +++ b/beacon-chain/core/altair/attestation_test.go @@ -5,6 +5,7 @@ import ( types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/beacon-chain/core/altair" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/state" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/shared/mathutil" @@ -117,6 +118,97 @@ func TestValidatorFlag_Add(t *testing.T) { } } +func TestSetEpochParticipation(t *testing.T) { + beaconState, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().MaxValidatorsPerCommittee) + cfg := params.BeaconConfig() + sourceFlagIndex := cfg.TimelySourceFlagIndex + targetFlagIndex := cfg.TimelyTargetFlagIndex + headFlagIndex := cfg.TimelyHeadFlagIndex + tests := []struct { + name string + indices []uint64 + epochParticipation []byte + participatedFlags map[uint8]bool + wantedNumerator uint64 + wantedEpochParticipation []byte + }{ + {name: "none participated", + indices: []uint64{}, epochParticipation: []byte{0, 0, 0, 0, 0, 0, 0, 0}, participatedFlags: map[uint8]bool{ + sourceFlagIndex: false, + targetFlagIndex: false, + headFlagIndex: false, + }, + wantedNumerator: 0, + wantedEpochParticipation: []byte{0, 0, 0, 0, 0, 0, 0, 0}, + }, + {name: "some participated without flags", + indices: []uint64{0, 1, 2, 3}, epochParticipation: []byte{0, 0, 0, 0, 0, 0, 0, 0}, participatedFlags: map[uint8]bool{ + sourceFlagIndex: false, + targetFlagIndex: false, + headFlagIndex: false, + }, + wantedNumerator: 0, + wantedEpochParticipation: []byte{0, 0, 0, 0, 0, 0, 0, 0}, + }, + {name: "some participated with some flags", + indices: []uint64{0, 1, 2, 3}, epochParticipation: []byte{0, 0, 0, 0, 0, 0, 0, 0}, participatedFlags: map[uint8]bool{ + sourceFlagIndex: true, + targetFlagIndex: true, + headFlagIndex: false, + }, + wantedNumerator: 40473600, + wantedEpochParticipation: []byte{3, 3, 3, 3, 0, 0, 0, 0}, + }, + {name: "all participated with some flags", + indices: []uint64{0, 1, 2, 3, 4, 5, 6, 7}, epochParticipation: []byte{0, 0, 0, 0, 0, 0, 0, 0}, participatedFlags: map[uint8]bool{ + sourceFlagIndex: true, + targetFlagIndex: false, + headFlagIndex: false, + }, + wantedNumerator: 28331520, + wantedEpochParticipation: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + }, + {name: "all participated with all flags", + indices: []uint64{0, 1, 2, 3, 4, 5, 6, 7}, epochParticipation: []byte{0, 0, 0, 0, 0, 0, 0, 0}, participatedFlags: map[uint8]bool{ + sourceFlagIndex: true, + targetFlagIndex: true, + headFlagIndex: true, + }, + wantedNumerator: 109278720, + wantedEpochParticipation: []byte{7, 7, 7, 7, 7, 7, 7, 7}, + }, + } + for _, test := range tests { + n, p, err := altair.EpochParticipation(beaconState, test.indices, test.epochParticipation, test.participatedFlags) + require.NoError(t, err) + require.Equal(t, test.wantedNumerator, n) + require.DeepSSZEqual(t, test.wantedEpochParticipation, p) + } +} + +func TestRewardProposer(t *testing.T) { + beaconState, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().MaxValidatorsPerCommittee) + require.NoError(t, beaconState.SetSlot(1)) + tests := []struct { + rewardNumerator uint64 + want uint64 + }{ + {rewardNumerator: 1, want: 32000000000}, + {rewardNumerator: 10000, want: 32000000022}, + {rewardNumerator: 1000000, want: 32000002254}, + {rewardNumerator: 1000000000, want: 32002234396}, + {rewardNumerator: 1000000000000, want: 34234377253}, + } + for _, test := range tests { + require.NoError(t, altair.RewardProposer(beaconState, test.rewardNumerator)) + i, err := helpers.BeaconProposerIndex(beaconState) + require.NoError(t, err) + b, err := beaconState.BalanceAtIndex(i) + require.NoError(t, err) + require.Equal(t, test.want, b) + } +} + func TestAttestationParticipationFlagIndices(t *testing.T) { beaconState, _ := testutil.DeterministicGenesisStateAltair(t, params.BeaconConfig().MaxValidatorsPerCommittee) require.NoError(t, beaconState.SetSlot(1))