From d1d1b73fb1783b564556a48ea86d69d8dd1003e7 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 21 Mar 2019 15:11:05 -0500 Subject: [PATCH 1/5] Simplify justification and finalization accounting logic Much of the simplification is cosmetic. The following changes are substantive: * Inactivity leak penalty specifically on missing the target, not both the target and the source * Even outside of quadratic leak scenarios, slashing victims suffer offline penalties --- specs/core/0_beacon-chain.md | 94 ++++++++---------------------------- 1 file changed, 21 insertions(+), 73 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 1067c3dc0f..e2cd8b162b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1883,10 +1883,11 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: ```python def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_since_finality: int) -> Gwei: - return ( - get_base_reward(state, index) + - get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 - ) + if epochs_since_finality <= 4: + extra_penalty = 0 + else: + extra_penalty = get_effective_balance(state, index) * min(epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 + return get_base_reward(state, index) + extra_penalty ``` Note: When applying penalties in the following balance recalculations implementers should make sure the `uint64` does not underflow. @@ -1896,22 +1897,8 @@ Note: When applying penalties in the following balance recalculations implemente ```python def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: epochs_since_finality = get_current_epoch(state) + 1 - state.finalized_epoch - if epochs_since_finality <= 4: - return compute_normal_justification_and_finalization_deltas(state) - else: - return compute_inactivity_leak_deltas(state) -``` - -When blocks are finalizing normally... - -```python -def compute_normal_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - # deltas[0] for rewards - # deltas[1] for penalties - deltas = [ - [0 for index in range(len(state.validator_registry))], - [0 for index in range(len(state.validator_registry))] - ] + rewards = [0 for index in range(len(state.validator_registry))] + penalties = [0 for index in range(len(state.validator_registry))] # Some helper variables boundary_attestations = get_previous_epoch_boundary_attestations(state) boundary_attesting_balance = get_attesting_balance(state, boundary_attestations) @@ -1919,76 +1906,37 @@ def compute_normal_justification_and_finalization_deltas(state: BeaconState) -> total_attesting_balance = get_attesting_balance(state, state.previous_epoch_attestations) matching_head_attestations = get_previous_epoch_matching_head_attestations(state) matching_head_balance = get_attesting_balance(state, matching_head_attestations) + eligible_validators = [ + i for i,v in enumerate(state.validator_registry) if is_active_validator(v, get_current_epoch(state)) or + (v.slashed and get_current_epoch(state) < v.withdrawable_epoch) + ] # Process rewards or penalties for all validators - for index in get_active_validator_indices(state.validator_registry, get_previous_epoch(state)): + for index in eligible_validators: # Expected FFG source if index in get_attesting_indices(state, state.previous_epoch_attestations): - deltas[0][index] += get_base_reward(state, index) * total_attesting_balance // total_balance + rewards[index] += get_base_reward(state, index) * total_attesting_balance // total_balance # Inclusion speed bonus - deltas[0][index] += ( + rewards[index] += ( get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_distance(state, index) ) else: - deltas[1][index] += get_base_reward(state, index) + penalties[index] += get_base_reward(state, index) # Expected FFG target if index in get_attesting_indices(state, boundary_attestations): - deltas[0][index] += get_base_reward(state, index) * boundary_attesting_balance // total_balance + rewards[index] += get_base_reward(state, index) * boundary_attesting_balance // total_balance else: - deltas[1][index] += get_base_reward(state, index) + penalties[index] += get_inactivity_penalty(state, index, epochs_since_finality) # Expected head if index in get_attesting_indices(state, matching_head_attestations): - deltas[0][index] += get_base_reward(state, index) * matching_head_balance // total_balance + rewards[index] += get_base_reward(state, index) * matching_head_balance // total_balance else: - deltas[1][index] += get_base_reward(state, index) + penalties[index] += get_base_reward(state, index) # Proposer bonus if index in get_attesting_indices(state, state.previous_epoch_attestations): proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index)) - deltas[0][proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT - return deltas -``` - -When blocks are not finalizing normally... - -```python -def compute_inactivity_leak_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - # deltas[0] for rewards - # deltas[1] for penalties - deltas = [ - [0 for index in range(len(state.validator_registry))], - [0 for index in range(len(state.validator_registry))] - ] - boundary_attestations = get_previous_epoch_boundary_attestations(state) - matching_head_attestations = get_previous_epoch_matching_head_attestations(state) - active_validator_indices = get_active_validator_indices(state.validator_registry, get_previous_epoch(state)) - epochs_since_finality = get_current_epoch(state) + 1 - state.finalized_epoch - for index in active_validator_indices: - if index not in get_attesting_indices(state, state.previous_epoch_attestations): - deltas[1][index] += get_inactivity_penalty(state, index, epochs_since_finality) - else: - # If a validator did attest, apply a small penalty for getting attestations included late - deltas[0][index] += ( - get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // - inclusion_distance(state, index) - ) - deltas[1][index] += get_base_reward(state, index) - if index not in get_attesting_indices(state, boundary_attestations): - deltas[1][index] += get_inactivity_penalty(state, index, epochs_since_finality) - if index not in get_attesting_indices(state, matching_head_attestations): - deltas[1][index] += get_base_reward(state, index) - # Penalize slashed-but-inactive validators as though they were active but offline - for index in range(len(state.validator_registry)): - eligible = ( - index not in active_validator_indices and - state.validator_registry[index].slashed and - get_current_epoch(state) < state.validator_registry[index].withdrawable_epoch - ) - if eligible: - deltas[1][index] += ( - 2 * get_inactivity_penalty(state, index, epochs_since_finality) + - get_base_reward(state, index) - ) - return deltas + rewards[proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT + return [rewards, penalties] ``` ##### Crosslinks From 38a5c3640b30581a4e807ae6aba13e7266bd1a76 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Thu, 21 Mar 2019 15:13:13 -0500 Subject: [PATCH 2/5] Re-added some penalization in case of failure to finalize --- specs/core/0_beacon-chain.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index e2cd8b162b..2a7b0c776f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1936,6 +1936,9 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[ if index in get_attesting_indices(state, state.previous_epoch_attestations): proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index)) rewards[proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT + # Take away max rewards if we're not finalizing + if epochs_since_finality > 4: + penalties[index] += get_base_reward(state, index) * 4 return [rewards, penalties] ``` From b34858c67b6c0df1bbaaf9c9d44dd68000ebb273 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 22 Mar 2019 14:21:33 +0800 Subject: [PATCH 3/5] Refactor `get_justification_and_finalization_deltas` --- specs/core/0_beacon-chain.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2a7b0c776f..b374b094fb 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -115,7 +115,7 @@ - [Helper functions](#helper-functions-1) - [Justification](#justification) - [Crosslinks](#crosslinks) - - [Eth1 data](#eth1-data-1) + - [Eth1 data](#eth1-data) - [Rewards and penalties](#rewards-and-penalties) - [Justification and finalization](#justification-and-finalization) - [Crosslinks](#crosslinks-1) @@ -128,7 +128,7 @@ - [Per-block processing](#per-block-processing) - [Block header](#block-header) - [RANDAO](#randao) - - [Eth1 data](#eth1-data) + - [Eth1 data](#eth1-data-1) - [Transactions](#transactions) - [Proposer slashings](#proposer-slashings) - [Attester slashings](#attester-slashings) @@ -1896,7 +1896,8 @@ Note: When applying penalties in the following balance recalculations implemente ```python def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: - epochs_since_finality = get_current_epoch(state) + 1 - state.finalized_epoch + current_epoch = get_current_epoch(state) + epochs_since_finality = current_epoch + 1 - state.finalized_epoch rewards = [0 for index in range(len(state.validator_registry))] penalties = [0 for index in range(len(state.validator_registry))] # Some helper variables @@ -1907,38 +1908,42 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[ matching_head_attestations = get_previous_epoch_matching_head_attestations(state) matching_head_balance = get_attesting_balance(state, matching_head_attestations) eligible_validators = [ - i for i,v in enumerate(state.validator_registry) if is_active_validator(v, get_current_epoch(state)) or - (v.slashed and get_current_epoch(state) < v.withdrawable_epoch) + index for index, validator in enumerate(state.validator_registry) + if ( + is_active_validator(validator, current_epoch) or + (validator.slashed and current_epoch < validator.withdrawable_epoch) + ) ] # Process rewards or penalties for all validators for index in eligible_validators: + base_reward = get_base_reward(state, index) # Expected FFG source if index in get_attesting_indices(state, state.previous_epoch_attestations): - rewards[index] += get_base_reward(state, index) * total_attesting_balance // total_balance + rewards[index] += base_reward * total_attesting_balance // total_balance # Inclusion speed bonus rewards[index] += ( - get_base_reward(state, index) * MIN_ATTESTATION_INCLUSION_DELAY // + base_reward * MIN_ATTESTATION_INCLUSION_DELAY // inclusion_distance(state, index) ) else: - penalties[index] += get_base_reward(state, index) + penalties[index] += base_reward # Expected FFG target if index in get_attesting_indices(state, boundary_attestations): - rewards[index] += get_base_reward(state, index) * boundary_attesting_balance // total_balance + rewards[index] += base_reward * boundary_attesting_balance // total_balance else: penalties[index] += get_inactivity_penalty(state, index, epochs_since_finality) # Expected head if index in get_attesting_indices(state, matching_head_attestations): - rewards[index] += get_base_reward(state, index) * matching_head_balance // total_balance + rewards[index] += base_reward * matching_head_balance // total_balance else: - penalties[index] += get_base_reward(state, index) + penalties[index] += base_reward # Proposer bonus if index in get_attesting_indices(state, state.previous_epoch_attestations): proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index)) - rewards[proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT + rewards[proposer_index] += base_reward // ATTESTATION_INCLUSION_REWARD_QUOTIENT # Take away max rewards if we're not finalizing if epochs_since_finality > 4: - penalties[index] += get_base_reward(state, index) * 4 + penalties[index] += base_reward * 4 return [rewards, penalties] ``` From e8257db32062a2a674bef8f1c4689d93ba5e0e26 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 22 Mar 2019 05:40:41 -0500 Subject: [PATCH 4/5] Removed hanging min --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index b374b094fb..a4719c7026 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1886,7 +1886,7 @@ def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_sin if epochs_since_finality <= 4: extra_penalty = 0 else: - extra_penalty = get_effective_balance(state, index) * min(epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 + extra_penalty = get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 return get_base_reward(state, index) + extra_penalty ``` From a38e3525cd27559cca9599c7c9cf7199b81b558b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 26 Mar 2019 13:18:18 -0600 Subject: [PATCH 5/5] ensure validator balances are losing when no finality --- tests/phase0/test_sanity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index b287bde51b..3b4497ca52 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -108,6 +108,8 @@ def test_empty_epoch_transition_not_finalizing(state): assert test_state.slot == block.slot assert test_state.finalized_epoch < get_current_epoch(test_state) - 4 + for index in range(len(test_state.validator_registry)): + assert get_balance(test_state, index) < get_balance(state, index) return state, [block], test_state