ensure when performing optimally that you don't lose money during a leak
This commit is contained in:
parent
4d6b99b024
commit
85e78223dd
|
@ -1354,6 +1354,19 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei:
|
||||||
return Gwei(effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH)
|
return Gwei(effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_finality_delay(state: BeaconState) -> uint64:
|
||||||
|
return get_previous_epoch(state) - state.finalized_checkpoint.epoch
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
def in_inactivity_leak(state: BeaconState) -> bool:
|
||||||
|
return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
|
def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
|
||||||
previous_epoch = get_previous_epoch(state)
|
previous_epoch = get_previous_epoch(state)
|
||||||
|
@ -1378,8 +1391,11 @@ def get_attestation_component_deltas(state: BeaconState,
|
||||||
for index in get_eligible_validator_indices(state):
|
for index in get_eligible_validator_indices(state):
|
||||||
if index in unslashed_attesting_indices:
|
if index in unslashed_attesting_indices:
|
||||||
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow
|
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from balance totals to avoid uint64 overflow
|
||||||
reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
|
if in_inactivity_leak(state):
|
||||||
rewards[index] += reward_numerator // (total_balance // increment)
|
rewards[index] += get_base_reward(state, index)
|
||||||
|
else:
|
||||||
|
reward_numerator = get_base_reward(state, index) * (attesting_balance // increment)
|
||||||
|
rewards[index] += reward_numerator // (total_balance // increment)
|
||||||
else:
|
else:
|
||||||
penalties[index] += get_base_reward(state, index)
|
penalties[index] += get_base_reward(state, index)
|
||||||
return rewards, penalties
|
return rewards, penalties
|
||||||
|
@ -1428,7 +1444,10 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ
|
||||||
], key=lambda a: a.inclusion_delay)
|
], key=lambda a: a.inclusion_delay)
|
||||||
proposer_reward = Gwei(get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT)
|
proposer_reward = Gwei(get_base_reward(state, index) // PROPOSER_REWARD_QUOTIENT)
|
||||||
rewards[attestation.proposer_index] += proposer_reward
|
rewards[attestation.proposer_index] += proposer_reward
|
||||||
max_attester_reward = get_base_reward(state, index) - proposer_reward
|
if in_inactivity_leak(state):
|
||||||
|
max_attester_reward = get_base_reward(state, index)
|
||||||
|
else:
|
||||||
|
max_attester_reward = get_base_reward(state, index) - proposer_reward
|
||||||
rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay)
|
rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay)
|
||||||
|
|
||||||
# No penalties associated with inclusion delay
|
# No penalties associated with inclusion delay
|
||||||
|
@ -1442,16 +1461,14 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S
|
||||||
Return inactivity reward/penalty deltas for each validator.
|
Return inactivity reward/penalty deltas for each validator.
|
||||||
"""
|
"""
|
||||||
penalties = [Gwei(0) for _ in range(len(state.validators))]
|
penalties = [Gwei(0) for _ in range(len(state.validators))]
|
||||||
finality_delay = get_previous_epoch(state) - state.finalized_checkpoint.epoch
|
if in_inactivity_leak(state):
|
||||||
|
|
||||||
if finality_delay > MIN_EPOCHS_TO_INACTIVITY_PENALTY:
|
|
||||||
matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state))
|
matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state))
|
||||||
matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations)
|
matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations)
|
||||||
for index in get_eligible_validator_indices(state):
|
for index in get_eligible_validator_indices(state):
|
||||||
penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index))
|
penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * get_base_reward(state, index))
|
||||||
if index not in matching_target_attesting_indices:
|
if index not in matching_target_attesting_indices:
|
||||||
effective_balance = state.validators[index].effective_balance
|
effective_balance = state.validators[index].effective_balance
|
||||||
penalties[index] += Gwei(effective_balance * finality_delay // INACTIVITY_PENALTY_QUOTIENT)
|
penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT)
|
||||||
|
|
||||||
# No rewards associated with inactivity penalties
|
# No rewards associated with inactivity penalties
|
||||||
rewards = [Gwei(0) for _ in range(len(state.validators))]
|
rewards = [Gwei(0) for _ in range(len(state.validators))]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from random import Random
|
from random import Random
|
||||||
|
from lru import LRU
|
||||||
|
|
||||||
from eth2spec.phase0 import spec as spec_phase0
|
from eth2spec.phase0 import spec as spec_phase0
|
||||||
from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations
|
from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations
|
||||||
|
@ -170,6 +171,39 @@ def run_get_inactivity_penalty_deltas(spec, state):
|
||||||
assert penalties[index] == 0
|
assert penalties[index] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def transition_state_to_leak(spec, state, epochs=None):
|
||||||
|
if epochs is None:
|
||||||
|
epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
||||||
|
assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
||||||
|
|
||||||
|
for _ in range(epochs):
|
||||||
|
next_epoch(spec, state)
|
||||||
|
|
||||||
|
|
||||||
|
_cache_dict = LRU(size=10)
|
||||||
|
|
||||||
|
|
||||||
|
def leaking(epochs=None):
|
||||||
|
|
||||||
|
def deco(fn):
|
||||||
|
def entry(*args, spec, state, **kw):
|
||||||
|
# If the pre-state is not already known in the LRU, then take it,
|
||||||
|
# transition it to leak, and put it in the LRU.
|
||||||
|
# The input state is likely already cached, so the hash-tree-root does not affect speed.
|
||||||
|
key = (state.hash_tree_root(), spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY, spec.SLOTS_PER_EPOCH, epochs)
|
||||||
|
global _cache_dict
|
||||||
|
if key not in _cache_dict:
|
||||||
|
transition_state_to_leak(spec, state, epochs=epochs)
|
||||||
|
_cache_dict[key] = state.get_backing() # cache the tree structure, not the view wrapping it.
|
||||||
|
|
||||||
|
# Take an entry out of the LRU.
|
||||||
|
# No copy is necessary, as we wrap the immutable backing with a new view.
|
||||||
|
state = spec.BeaconState(backing=_cache_dict[key])
|
||||||
|
return fn(*args, spec=spec, state=state, **kw)
|
||||||
|
return entry
|
||||||
|
return deco
|
||||||
|
|
||||||
|
|
||||||
def set_some_new_deposits(spec, state, rng):
|
def set_some_new_deposits(spec, state, rng):
|
||||||
num_validators = len(state.validators)
|
num_validators = len(state.validators)
|
||||||
# Set ~1/10 to just recently deposited
|
# Set ~1/10 to just recently deposited
|
||||||
|
|
|
@ -14,6 +14,7 @@ from eth2spec.test.helpers.attestations import (
|
||||||
get_valid_attestation,
|
get_valid_attestation,
|
||||||
prepare_state_with_attestations,
|
prepare_state_with_attestations,
|
||||||
)
|
)
|
||||||
|
from eth2spec.test.helpers.rewards import leaking
|
||||||
from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants
|
from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants
|
||||||
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
|
from eth2spec.test.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
|
||||||
from random import Random
|
from random import Random
|
||||||
|
@ -80,6 +81,56 @@ def test_full_attestations(spec, state):
|
||||||
assert state.balances[index] < pre_state.balances[index]
|
assert state.balances[index] < pre_state.balances[index]
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
@leaking()
|
||||||
|
def test_full_attestations_with_leak(spec, state):
|
||||||
|
attestations = prepare_state_with_attestations(spec, state)
|
||||||
|
|
||||||
|
proposer_indices = [a.proposer_index for a in state.previous_epoch_attestations]
|
||||||
|
pre_state = state.copy()
|
||||||
|
|
||||||
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
|
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
|
||||||
|
assert len(attesting_indices) == len(pre_state.validators)
|
||||||
|
for index in range(len(pre_state.validators)):
|
||||||
|
# Proposers can still make money during a leak
|
||||||
|
if index in proposer_indices:
|
||||||
|
assert state.balances[index] > pre_state.balances[index]
|
||||||
|
# If not proposer but participated optimally, should have exactly neutral balance
|
||||||
|
elif index in attesting_indices:
|
||||||
|
assert state.balances[index] == pre_state.balances[index]
|
||||||
|
else:
|
||||||
|
assert state.balances[index] < pre_state.balances[index]
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
@leaking()
|
||||||
|
def test_partial_attestations_with_leak(spec, state):
|
||||||
|
attestations = prepare_state_with_attestations(spec, state)
|
||||||
|
|
||||||
|
attestations = attestations[:len(attestations) // 2]
|
||||||
|
state.previous_epoch_attestations = state.previous_epoch_attestations[:len(attestations)]
|
||||||
|
proposer_indices = [a.proposer_index for a in state.previous_epoch_attestations]
|
||||||
|
pre_state = state.copy()
|
||||||
|
|
||||||
|
yield from run_process_rewards_and_penalties(spec, state)
|
||||||
|
|
||||||
|
attesting_indices = spec.get_unslashed_attesting_indices(state, attestations)
|
||||||
|
assert len(attesting_indices) < len(pre_state.validators)
|
||||||
|
for index in range(len(pre_state.validators)):
|
||||||
|
# Proposers can still make money during a leak
|
||||||
|
if index in proposer_indices and index in attesting_indices:
|
||||||
|
assert state.balances[index] > pre_state.balances[index]
|
||||||
|
# If not proposer but participated optimally, should have exactly neutral balance
|
||||||
|
elif index in attesting_indices:
|
||||||
|
assert state.balances[index] == pre_state.balances[index]
|
||||||
|
else:
|
||||||
|
assert state.balances[index] < pre_state.balances[index]
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_full_attestations_random_incorrect_fields(spec, state):
|
def test_full_attestations_random_incorrect_fields(spec, state):
|
||||||
|
|
|
@ -1,40 +1,6 @@
|
||||||
from eth2spec.test.context import with_all_phases, spec_state_test
|
from eth2spec.test.context import with_all_phases, spec_state_test
|
||||||
from eth2spec.test.helpers.state import next_epoch
|
from eth2spec.test.helpers.rewards import leaking
|
||||||
import eth2spec.test.helpers.rewards as rewards_helpers
|
import eth2spec.test.helpers.rewards as rewards_helpers
|
||||||
from lru import LRU
|
|
||||||
|
|
||||||
|
|
||||||
def transition_state_to_leak(spec, state, epochs=None):
|
|
||||||
if epochs is None:
|
|
||||||
epochs = spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
|
||||||
assert epochs >= spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY
|
|
||||||
|
|
||||||
for _ in range(epochs):
|
|
||||||
next_epoch(spec, state)
|
|
||||||
|
|
||||||
|
|
||||||
_cache_dict = LRU(size=10)
|
|
||||||
|
|
||||||
|
|
||||||
def leaking(epochs=None):
|
|
||||||
|
|
||||||
def deco(fn):
|
|
||||||
def entry(*args, spec, state, **kw):
|
|
||||||
# If the pre-state is not already known in the LRU, then take it,
|
|
||||||
# transition it to leak, and put it in the LRU.
|
|
||||||
# The input state is likely already cached, so the hash-tree-root does not affect speed.
|
|
||||||
key = (state.hash_tree_root(), spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY, spec.SLOTS_PER_EPOCH, epochs)
|
|
||||||
global _cache_dict
|
|
||||||
if key not in _cache_dict:
|
|
||||||
transition_state_to_leak(spec, state, epochs=epochs)
|
|
||||||
_cache_dict[key] = state.get_backing() # cache the tree structure, not the view wrapping it.
|
|
||||||
|
|
||||||
# Take an entry out of the LRU.
|
|
||||||
# No copy is necessary, as we wrap the immutable backing with a new view.
|
|
||||||
state = spec.BeaconState(backing=_cache_dict[key])
|
|
||||||
return fn(*args, spec=spec, state=state, **kw)
|
|
||||||
return entry
|
|
||||||
return deco
|
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
|
|
Loading…
Reference in New Issue