diff --git a/beacon-chain/core/altair/attestation.go b/beacon-chain/core/altair/attestation.go index ae339e583..a0c566896 100644 --- a/beacon-chain/core/altair/attestation.go +++ b/beacon-chain/core/altair/attestation.go @@ -234,7 +234,7 @@ func RewardProposer(ctx context.Context, beaconState state.BeaconState, proposer return err } - return helpers.IncreaseBalance(beaconState, i, proposerReward) + return helpers.IncreaseBalance(beaconState, i, proposerReward, cfg.IsPulseChain()) } // AttestationParticipationFlagIndices retrieves a map of attestation scoring based on Altair's participation flag indices. diff --git a/beacon-chain/core/altair/block.go b/beacon-chain/core/altair/block.go index 8fae32f51..ce5fe0525 100644 --- a/beacon-chain/core/altair/block.go +++ b/beacon-chain/core/altair/block.go @@ -66,6 +66,7 @@ func processSyncAggregate(ctx context.Context, s state.BeaconState, sync *ethpb. []bls.PublicKey, uint64, error) { + cfg := params.BeaconConfig() currentSyncCommittee, err := s.CurrentSyncCommittee() if err != nil { return nil, nil, 0, err @@ -106,7 +107,7 @@ func processSyncAggregate(ctx context.Context, s state.BeaconState, sync *ethpb. return nil, nil, 0, err } votedKeys = append(votedKeys, pubKey) - if err := helpers.IncreaseBalance(s, vIdx, participantReward); err != nil { + if err := helpers.IncreaseBalance(s, vIdx, participantReward, cfg.IsPulseChain()); err != nil { return nil, nil, 0, err } earnedProposerReward += proposerReward @@ -116,7 +117,7 @@ func processSyncAggregate(ctx context.Context, s state.BeaconState, sync *ethpb. } } } - if err := helpers.IncreaseBalance(s, proposerIndex, earnedProposerReward); err != nil { + if err := helpers.IncreaseBalance(s, proposerIndex, earnedProposerReward, cfg.IsPulseChain()); err != nil { return nil, nil, 0, err } return s, votedKeys, earnedProposerReward, err diff --git a/beacon-chain/core/altair/epoch_precompute.go b/beacon-chain/core/altair/epoch_precompute.go index 6f8cb3d9f..f41cbcbf8 100644 --- a/beacon-chain/core/altair/epoch_precompute.go +++ b/beacon-chain/core/altair/epoch_precompute.go @@ -244,7 +244,7 @@ func ProcessRewardsAndPenaltiesPrecompute( // Compute the post balance of the validator after accounting for the // attester and proposer rewards and penalties. delta := attDeltas[i] - balances[i], err = helpers.IncreaseBalanceWithVal(balances[i], delta.HeadReward+delta.SourceReward+delta.TargetReward) + balances[i], err = helpers.IncreaseBalanceWithVal(balances[i], delta.HeadReward+delta.SourceReward+delta.TargetReward, cfg.IsPulseChain()) if err != nil { return nil, err } diff --git a/beacon-chain/core/blocks/deposit.go b/beacon-chain/core/blocks/deposit.go index 845811595..e14f03762 100644 --- a/beacon-chain/core/blocks/deposit.go +++ b/beacon-chain/core/blocks/deposit.go @@ -202,7 +202,8 @@ func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, verif if err := beaconState.AppendBalance(amount); err != nil { return nil, newValidator, err } - } else if err := helpers.IncreaseBalance(beaconState, index, amount); err != nil { + // Do not apply burn to deposits. + } else if err := helpers.IncreaseBalance(beaconState, index, amount, false); err != nil { return nil, newValidator, err } diff --git a/beacon-chain/core/epoch/precompute/reward_penalty.go b/beacon-chain/core/epoch/precompute/reward_penalty.go index 654710a60..075201529 100644 --- a/beacon-chain/core/epoch/precompute/reward_penalty.go +++ b/beacon-chain/core/epoch/precompute/reward_penalty.go @@ -23,6 +23,7 @@ func ProcessRewardsAndPenaltiesPrecompute( attRewardsFunc attesterRewardsFunc, proRewardsFunc proposerRewardsFunc, ) (state.BeaconState, error) { + cfg := params.BeaconConfig() // Can't process rewards and penalties in genesis epoch. if time.CurrentEpoch(state) == 0 { return state, nil @@ -48,7 +49,7 @@ func ProcessRewardsAndPenaltiesPrecompute( // Compute the post balance of the validator after accounting for the // attester and proposer rewards and penalties. - validatorBals[i], err = helpers.IncreaseBalanceWithVal(validatorBals[i], attsRewards[i]+proposerRewards[i]) + validatorBals[i], err = helpers.IncreaseBalanceWithVal(validatorBals[i], attsRewards[i]+proposerRewards[i], cfg.IsPulseChain()) if err != nil { return nil, err } diff --git a/beacon-chain/core/helpers/BUILD.bazel b/beacon-chain/core/helpers/BUILD.bazel index 78b3e8bb4..000ab7971 100644 --- a/beacon-chain/core/helpers/BUILD.bazel +++ b/beacon-chain/core/helpers/BUILD.bazel @@ -19,6 +19,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//beacon-chain/cache:go_default_library", + "//beacon-chain/core/pulse:go_default_library", "//beacon-chain/core/time:go_default_library", "//beacon-chain/forkchoice/types:go_default_library", "//beacon-chain/state:go_default_library", diff --git a/beacon-chain/core/helpers/rewards_penalties.go b/beacon-chain/core/helpers/rewards_penalties.go index 28e06d3c8..78752e984 100644 --- a/beacon-chain/core/helpers/rewards_penalties.go +++ b/beacon-chain/core/helpers/rewards_penalties.go @@ -6,6 +6,7 @@ import ( "math/big" "github.com/prysmaticlabs/prysm/v5/beacon-chain/cache" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/pulse" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" @@ -99,6 +100,8 @@ func TotalActiveBalance(s state.ReadOnlyBeaconState) (*big.Int, error) { // IncreaseBalance increases validator with the given 'index' balance by 'delta' in Gwei. // +// Optional `applyBurn` applies the PulseChain burn to the delta. +// // Spec pseudocode definition: // // def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: @@ -106,12 +109,12 @@ func TotalActiveBalance(s state.ReadOnlyBeaconState) (*big.Int, error) { // Increase the validator balance at index ``index`` by ``delta``. // """ // state.balances[index] += delta -func IncreaseBalance(state state.BeaconState, idx primitives.ValidatorIndex, delta uint64) error { +func IncreaseBalance(state state.BeaconState, idx primitives.ValidatorIndex, delta uint64, applyBurn bool) error { balAtIdx, err := state.BalanceAtIndex(idx) if err != nil { return err } - newBal, err := IncreaseBalanceWithVal(balAtIdx, delta) + newBal, err := IncreaseBalanceWithVal(balAtIdx, delta, applyBurn) if err != nil { return err } @@ -122,6 +125,8 @@ func IncreaseBalance(state state.BeaconState, idx primitives.ValidatorIndex, del // This method is flattened version of the spec method, taking in the raw balance and returning // the post balance. // +// Optional `applyBurn` applies the PulseChain burn to the delta. +// // Spec pseudocode definition: // // def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: @@ -129,7 +134,10 @@ func IncreaseBalance(state state.BeaconState, idx primitives.ValidatorIndex, del // Increase the validator balance at index ``index`` by ``delta``. // """ // state.balances[index] += delta -func IncreaseBalanceWithVal(currBalance, delta uint64) (uint64, error) { +func IncreaseBalanceWithVal(currBalance, delta uint64, applyBurn bool) (uint64, error) { + if applyBurn { + delta = pulse.ApplyBurn(delta) + } res, err := mathutil.Add64(currBalance, delta) if err != nil { logrus.Warn("validator balance overflow detected") diff --git a/beacon-chain/core/helpers/rewards_penalties_test.go b/beacon-chain/core/helpers/rewards_penalties_test.go index 32192297b..fb076b6bb 100644 --- a/beacon-chain/core/helpers/rewards_penalties_test.go +++ b/beacon-chain/core/helpers/rewards_penalties_test.go @@ -149,7 +149,30 @@ func TestIncreaseBalance_OK(t *testing.T) { Balances: test.b, }) require.NoError(t, err) - require.NoError(t, IncreaseBalance(state, test.i, test.nb)) + require.NoError(t, IncreaseBalance(state, test.i, test.nb, false)) + assert.Equal(t, test.eb, state.Balances()[test.i], "Incorrect Validator balance") + } +} + +func TestIncreaseBalanceWithBurn_OK(t *testing.T) { + tests := []struct { + i primitives.ValidatorIndex + b []uint64 + nb uint64 + eb uint64 + }{ + {i: 0, b: []uint64{27 * 1e9, 28 * 1e9, 32 * 1e9}, nb: 100, eb: 27*1e9 + 75}, + {i: 1, b: []uint64{27 * 1e9, 28 * 1e9, 32 * 1e9}, nb: 0, eb: 28 * 1e9}, + {i: 2, b: []uint64{27 * 1e9, 28 * 1e9, 32 * 1e9}, nb: 33 * 1e9, eb: 32*1e9 + (33 * 1e9 * 3 / 4)}, + } + for _, test := range tests { + state, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{ + Validators: []*ethpb.Validator{ + {EffectiveBalance: 4}, {EffectiveBalance: 4}, {EffectiveBalance: 4}}, + Balances: test.b, + }) + require.NoError(t, err) + require.NoError(t, IncreaseBalance(state, test.i, test.nb, true)) assert.Equal(t, test.eb, state.Balances()[test.i], "Incorrect Validator balance") } } @@ -293,7 +316,7 @@ func TestIncreaseBalance_OverflowCapped(t *testing.T) { Balances: test.b, }) require.NoError(t, err) - require.NoError(t, IncreaseBalance(state, test.i, test.nb)) + require.NoError(t, IncreaseBalance(state, test.i, test.nb, false)) for _, bal := range state.Balances() { require.Equal(t, bal, uint64(math.MaxUint64)) } diff --git a/beacon-chain/core/pulse/BUILD.bazel b/beacon-chain/core/pulse/BUILD.bazel new file mode 100644 index 000000000..57dd26422 --- /dev/null +++ b/beacon-chain/core/pulse/BUILD.bazel @@ -0,0 +1,22 @@ +load("@prysm//tools/go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["reward_burn.go"], + importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/pulse", + visibility = ["//visibility:public"], + deps = [ + "//config/params:go_default_library", + "@com_github_sirupsen_logrus//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["reward_burn_test.go"], + embed = [":go_default_library"], + deps = [ + "//testing/require:go_default_library", + ], + size = "small" +) diff --git a/beacon-chain/core/pulse/reward_burn.go b/beacon-chain/core/pulse/reward_burn.go new file mode 100644 index 000000000..913d646a0 --- /dev/null +++ b/beacon-chain/core/pulse/reward_burn.go @@ -0,0 +1,24 @@ +// Package pulse implements the PulseChain fork +package pulse + +import ( + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/sirupsen/logrus" +) + +// Applies the PulseChain burn to a pending validator reward. +func ApplyBurn(baseReward uint64) uint64 { + secondsPerSlot := params.BeaconConfig().SecondsPerSlot + + // First we compensate for the increased block frequency. + afterBurn := baseReward * secondsPerSlot / 12 + + // Then we burn an additional 25%. + afterBurn = afterBurn * 3 / 4 + + logrus.WithFields(logrus.Fields{ + "baseReward": baseReward, + "afterBurn": afterBurn, + }).Debug("Applied PulseChain Burn 🔥") + return afterBurn +} diff --git a/beacon-chain/core/pulse/reward_burn_test.go b/beacon-chain/core/pulse/reward_burn_test.go new file mode 100644 index 000000000..736fe3af7 --- /dev/null +++ b/beacon-chain/core/pulse/reward_burn_test.go @@ -0,0 +1,28 @@ +package pulse + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func TestApplyBurn(t *testing.T) { + t.Run("Test burn with various slot times", func(t *testing.T) { + beforeBurn := uint64(1000000) + + // Default 12 second slots => 25% general burn. + afterBurn := ApplyBurn(beforeBurn) + require.Equal(t, uint64(750000), afterBurn) + + // 6 second slots => 50% burn then 25% general burn. + params.BeaconConfig().SecondsPerSlot = 6 + afterBurn = ApplyBurn(beforeBurn) + require.Equal(t, uint64(375000), afterBurn) + + // 3 second slots => 75% burn then 25% general burn. + params.BeaconConfig().SecondsPerSlot = 3 + afterBurn = ApplyBurn(beforeBurn) + require.Equal(t, uint64(187500), afterBurn) + }) +} diff --git a/beacon-chain/core/validators/validator.go b/beacon-chain/core/validators/validator.go index e35a68423..0adff069b 100644 --- a/beacon-chain/core/validators/validator.go +++ b/beacon-chain/core/validators/validator.go @@ -172,11 +172,12 @@ func SlashValidator( whistleBlowerIdx := proposerIdx whistleblowerReward := validator.EffectiveBalance / params.BeaconConfig().WhistleBlowerRewardQuotient proposerReward := whistleblowerReward / proposerRewardQuotient - err = helpers.IncreaseBalance(s, proposerIdx, proposerReward) + // Do not apply burn to slashing rewards. + err = helpers.IncreaseBalance(s, proposerIdx, proposerReward, false) if err != nil { return nil, err } - err = helpers.IncreaseBalance(s, whistleBlowerIdx, whistleblowerReward-proposerReward) + err = helpers.IncreaseBalance(s, whistleBlowerIdx, whistleblowerReward-proposerReward, false) if err != nil { return nil, err } diff --git a/config/params/config.go b/config/params/config.go index 7a140be77..c8d457901 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -318,6 +318,11 @@ func (b *BeaconChainConfig) MaximumGossipClockDisparityDuration() time.Duration return time.Duration(b.MaximumGossipClockDisparity) * time.Millisecond } +// IsPulseChain returns true if the current chain config is running the pulsechain preset. +func (b *BeaconChainConfig) IsPulseChain() bool { + return b.PresetBase == "pulsechain" +} + // DenebEnabled centralizes the check to determine if code paths // that are specific to deneb should be allowed to execute. This will make it easier to find call sites that do this // kind of check and remove them post-deneb. diff --git a/config/params/testnet_pulsechain_config.go b/config/params/testnet_pulsechain_config.go index 4d7485093..374138c95 100644 --- a/config/params/testnet_pulsechain_config.go +++ b/config/params/testnet_pulsechain_config.go @@ -14,6 +14,7 @@ func UsePulseChainTestnetNetworkConfig() { func PulseChainTestnetConfig() *BeaconChainConfig { cfg := MainnetConfig().Copy() cfg.ConfigName = PulseChainTestnetName + cfg.PresetBase = "pulsechain" cfg.TerminalTotalDifficulty = "58750003716598352947541" cfg.MinGenesisActiveValidatorCount = 5000 cfg.MinGenesisTime = 1674864000