From 78040ac3ae8572a856880320f849dc62bc4bacff Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 29 Oct 2021 12:39:29 -0600 Subject: [PATCH 1/4] update penalty params for Merge --- presets/mainnet/merge.yaml | 9 ++ presets/minimal/merge.yaml | 9 ++ specs/merge/beacon-chain.md | 109 +++++++++++++++++- .../test/helpers/proposer_slashings.py | 6 +- .../pyspec/eth2spec/test/helpers/rewards.py | 15 ++- .../test_process_slashings.py | 6 +- .../unittests/test_config_invariants.py | 6 +- 7 files changed, 145 insertions(+), 15 deletions(-) diff --git a/presets/mainnet/merge.yaml b/presets/mainnet/merge.yaml index 3deac4574c..a09822f2c7 100644 --- a/presets/mainnet/merge.yaml +++ b/presets/mainnet/merge.yaml @@ -1,5 +1,14 @@ # Mainnet preset - The Merge +# Updated penalty values +# --------------------------------------------------------------- +# 2**24 (= 16,777,216) +INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216 +# 2**5 (= 32) +MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32 +# 2 +PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3 + # Execution # --------------------------------------------------------------- # 2**30 (= 1,073,741,824) diff --git a/presets/minimal/merge.yaml b/presets/minimal/merge.yaml index 5df32ff579..01a8e56b34 100644 --- a/presets/minimal/merge.yaml +++ b/presets/minimal/merge.yaml @@ -1,5 +1,14 @@ # Minimal preset - The Merge +# Updated penalty values +# --------------------------------------------------------------- +# 2**24 (= 16,777,216) +INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216 +# 2**5 (= 32) +MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32 +# 2 +PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3 + # Execution # --------------------------------------------------------------- # 2**30 (= 1,073,741,824) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index aa2a771c5f..50c72d4e1e 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -12,6 +12,8 @@ - [Custom types](#custom-types) - [Constants](#constants) - [Execution](#execution) +- [Preset](#preset) + - [Updated penalty values](#updated-penalty-values) - [Configuration](#configuration) - [Transition settings](#transition-settings) - [Containers](#containers) @@ -28,13 +30,19 @@ - [`is_execution_enabled`](#is_execution_enabled) - [Misc](#misc) - [`compute_timestamp_at_slot`](#compute_timestamp_at_slot) + - [Beacon state accessors](#beacon-state-accessors) + - [Modified `get_inactivity_penalty_deltas`](#modified-get_inactivity_penalty_deltas) + - [Beacon state mutators](#beacon-state-mutators) + - [Modified `slash_validator`](#modified-slash_validator) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Execution engine](#execution-engine) - [`execute_payload`](#execute_payload) - [Block processing](#block-processing) - - [Execution payload processing](#execution-payload-processing) - - [`is_valid_gas_limit`](#is_valid_gas_limit) - - [`process_execution_payload`](#process_execution_payload) + - [Execution payload](#execution-payload) + - [`is_valid_gas_limit`](#is_valid_gas_limit) + - [`process_execution_payload`](#process_execution_payload) + - [Epoch processing](#epoch-processing) + - [Slashings](#slashings) - [Testing](#testing) @@ -66,6 +74,20 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) | | `MAX_EXTRA_DATA_BYTES` | `2**5` (= 32) | +## Preset + +### Updated penalty values + +The Merge updates a few configuration values to move penalty parameters to their final, maximum security values. + +*Note*: The spec does *not* override previous configuration values but instead creates new values and replaces usage throughout. + +| Name | Value | +| - | - | +| `INACTIVITY_PENALTY_QUOTIENT_MERGE` | `uint64(2**24)` (= 16,777,216) | +| `MIN_SLASHING_PENALTY_QUOTIENT_MERGE` | `uint64(2**5)` (= 32) | +| `PROPORTIONAL_SLASHING_MULTIPLIER_MERGE` | `uint64(3)` | + ## Configuration ### Transition settings @@ -223,6 +245,62 @@ def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) ``` +### Beacon state accessors + +#### Modified `get_inactivity_penalty_deltas` + +*Note*: The function `get_inactivity_penalty_deltas` is modified to use `INACTIVITY_PENALTY_QUOTIENT_MERGE`. + +```python +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. + """ + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + previous_epoch = get_previous_epoch(state) + matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) + for index in get_eligible_validator_indices(state): + if index not in matching_target_indices: + penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE + penalties[index] += Gwei(penalty_numerator // penalty_denominator) + return rewards, penalties +``` + +### Beacon state mutators + +#### Modified `slash_validator` + +*Note*: The function `slash_validator` is modified to use `MIN_SLASHING_PENALTY_QUOTIENT_MERGE`. + +```python +def slash_validator(state: BeaconState, + slashed_index: ValidatorIndex, + whistleblower_index: ValidatorIndex=None) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) +``` + + + ## Beacon chain state transition function ### Execution engine @@ -262,9 +340,9 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_sync_aggregate(state, block.body.sync_aggregate) ``` -### Execution payload processing +#### Execution payload -#### `is_valid_gas_limit` +##### `is_valid_gas_limit` ```python def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> bool: @@ -287,7 +365,7 @@ def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader return True ``` -#### `process_execution_payload` +##### `process_execution_payload` ```python def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: @@ -322,6 +400,25 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe ) ``` +### Epoch processing + +#### Slashings + +*Note*: The function `process_slashings` is modified to use `PROPORTIONAL_SLASHING_MULTIPLIER_MERGE`. + +```python +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, total_balance) + for index, validator in enumerate(state.validators): + if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow + penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance + penalty = penalty_numerator // total_balance * increment + decrease_balance(state, ValidatorIndex(index), penalty) +``` + ## Testing *Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Merge testing only. diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index ac0a1cce2d..6a8cd2dcb6 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -1,4 +1,4 @@ -from eth2spec.test.context import is_post_altair +from eth2spec.test.context import is_post_altair, is_post_merge from eth2spec.test.helpers.block_header import sign_block_header from eth2spec.test.helpers.keys import pubkey_to_privkey from eth2spec.test.helpers.state import get_balance @@ -9,7 +9,9 @@ def get_min_slashing_penalty_quotient(spec): - if is_post_altair(spec): + if is_post_merge(spec): + return spec.MIN_SLASHING_PENALTY_QUOTIENT_MERGE + elif is_post_altair(spec): return spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR else: return spec.MIN_SLASHING_PENALTY_QUOTIENT diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index ec617bda9b..49a7b0cd10 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -2,7 +2,7 @@ from lru import LRU from eth2spec.phase0.mainnet import VALIDATOR_REGISTRY_LIMIT # equal everywhere, fine to import -from eth2spec.test.context import is_post_altair +from eth2spec.test.context import is_post_altair, is_post_merge from eth2spec.test.helpers.state import ( next_epoch, ) @@ -21,6 +21,15 @@ class Deltas(Container): penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT] +def get_inactivity_penalty_quotient(spec): + if is_post_merge(spec): + return spec.INACTIVITY_PENALTY_QUOTIENT_MERGE + elif is_post_altair(spec): + return spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR + else: + return spec.INACTIVITY_PENALTY_QUOTIENT + + def has_enough_for_reward(spec, state, index): """ Check if base_reward will be non-zero. @@ -45,7 +54,7 @@ def has_enough_for_leak_penalty(spec, state, index): if is_post_altair(spec): return ( state.validators[index].effective_balance * state.inactivity_scores[index] - > spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR + > spec.config.INACTIVITY_SCORE_BIAS * get_inactivity_penalty_quotient(spec) ) else: return ( @@ -266,7 +275,7 @@ def run_get_inactivity_penalty_deltas(spec, state): else: # copied from spec: penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] - penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR + penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * get_inactivity_penalty_quotient(spec) assert penalties[index] == penalty_numerator // penalty_denominator diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index 1b977640d5..d3593d9ac6 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -1,5 +1,5 @@ from random import Random -from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair +from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair, is_post_merge from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, run_epoch_processing_to ) @@ -31,7 +31,9 @@ def slash_validators(spec, state, indices, out_epochs): def get_slashing_multiplier(spec): - if is_post_altair(spec): + if is_post_merge(spec): + return spec.PROPORTIONAL_SLASHING_MULTIPLIER_MERGE + elif is_post_altair(spec): return spec.PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR else: return spec.PROPORTIONAL_SLASHING_MULTIPLIER diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py index b39b011b4f..3183904c8b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py @@ -1,7 +1,7 @@ from eth2spec.test.context import ( spec_state_test, with_all_phases, - is_post_altair, + is_post_altair, is_post_merge, ) from eth2spec.test.helpers.constants import MAX_UINT_64 @@ -52,7 +52,9 @@ def test_hysteresis_quotient(spec, state): @spec_state_test def test_incentives(spec, state): # Ensure no ETH is minted in slash_validator - if is_post_altair(spec): + if is_post_merge(spec): + assert spec.MIN_SLASHING_PENALTY_QUOTIENT_MERGE <= spec.WHISTLEBLOWER_REWARD_QUOTIENT + elif is_post_altair(spec): assert spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR <= spec.WHISTLEBLOWER_REWARD_QUOTIENT else: assert spec.MIN_SLASHING_PENALTY_QUOTIENT <= spec.WHISTLEBLOWER_REWARD_QUOTIENT From 4da53e62d9c75e921892cc7e95ba2dba64551436 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 29 Oct 2021 14:04:25 -0600 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Diederik Loerakker --- presets/minimal/merge.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presets/minimal/merge.yaml b/presets/minimal/merge.yaml index 01a8e56b34..f92674a183 100644 --- a/presets/minimal/merge.yaml +++ b/presets/minimal/merge.yaml @@ -6,7 +6,7 @@ INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216 # 2**5 (= 32) MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32 -# 2 +# 3 PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3 # Execution From fe8d6edfe3c60bc45d966c32d00d57a0f9141016 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 31 Oct 2021 09:08:55 -0600 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Diederik Loerakker --- presets/mainnet/merge.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presets/mainnet/merge.yaml b/presets/mainnet/merge.yaml index a09822f2c7..4ee3cf8377 100644 --- a/presets/mainnet/merge.yaml +++ b/presets/mainnet/merge.yaml @@ -6,7 +6,7 @@ INACTIVITY_PENALTY_QUOTIENT_MERGE: 16777216 # 2**5 (= 32) MIN_SLASHING_PENALTY_QUOTIENT_MERGE: 32 -# 2 +# 3 PROPORTIONAL_SLASHING_MULTIPLIER_MERGE: 3 # Execution From d5f5f8bf6155a252c2f32f4a59da67751463565f Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 31 Oct 2021 09:47:58 -0600 Subject: [PATCH 4/4] add modified in merge --- specs/merge/beacon-chain.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 50c72d4e1e..d164b5564e 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -50,7 +50,10 @@ ## Introduction -This patch adds transaction execution to the beacon chain as part of the Merge fork. +This upgrade adds transaction execution to the beacon chain as part of the Merge fork. + +Additionally, this upgrade introduces the following minor changes: +* Penalty parameter updates to their planned maximally punitive values ## Custom types @@ -263,6 +266,7 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S for index in get_eligible_validator_indices(state): if index not in matching_target_indices: penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + # [Modified in Merge] penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_MERGE penalties[index] += Gwei(penalty_numerator // penalty_denominator) return rewards, penalties @@ -287,7 +291,8 @@ def slash_validator(state: BeaconState, validator.slashed = True validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE) + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_MERGE # [Modified in Merge] + decrease_balance(state, slashed_index, slashing_penalty) # Apply proposer and whistleblower rewards proposer_index = get_beacon_proposer_index(state) @@ -410,7 +415,10 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe def process_slashings(state: BeaconState) -> None: epoch = get_current_epoch(state) total_balance = get_total_active_balance(state) - adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, total_balance) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_MERGE, # [Modified in Merge] + total_balance + ) for index, validator in enumerate(state.validators): if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow