ensure when performing optimally that you don't lose money during a leak

This commit is contained in:
Danny Ryan
2020-05-19 16:51:46 -06:00
parent 4d6b99b024
commit 85e78223dd
4 changed files with 110 additions and 42 deletions

View File

@@ -1,4 +1,5 @@
from random import Random
from lru import LRU
from eth2spec.phase0 import spec as spec_phase0
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
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):
num_validators = len(state.validators)
# Set ~1/10 to just recently deposited

View File

@@ -14,6 +14,7 @@ from eth2spec.test.helpers.attestations import (
get_valid_attestation,
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.phase_0.epoch_processing.run_epoch_process_base import run_epoch_processing_with
from random import Random
@@ -80,6 +81,56 @@ def test_full_attestations(spec, state):
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
@spec_state_test
def test_full_attestations_random_incorrect_fields(spec, state):

View File

@@ -1,40 +1,6 @@
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
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