mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-01-12 11:44:41 +00:00
Merge pull request #2580 from ralexstokes/add-rewards-test-case-for-exited-vals
Add test cases to ensure coverage with exited validators
This commit is contained in:
commit
4faff4f899
@ -12,7 +12,6 @@ from eth2spec.test.helpers.constants import (
|
||||
from eth2spec.test.helpers.sync_committee import (
|
||||
compute_aggregate_sync_committee_signature,
|
||||
compute_committee_indices,
|
||||
get_committee_indices,
|
||||
run_sync_committee_processing,
|
||||
run_successful_sync_committee_test,
|
||||
)
|
||||
@ -28,7 +27,7 @@ from eth2spec.test.context import (
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_invalid_signature_bad_domain(spec, state):
|
||||
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
|
||||
block = build_empty_block_for_next_slot(spec, state)
|
||||
block.body.sync_aggregate = spec.SyncAggregate(
|
||||
@ -48,7 +47,7 @@ def test_invalid_signature_bad_domain(spec, state):
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_invalid_signature_missing_participant(spec, state):
|
||||
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
rng = random.Random(2020)
|
||||
random_participant = rng.choice(committee_indices)
|
||||
|
||||
@ -111,7 +110,7 @@ def test_invalid_signature_infinite_signature_with_single_participant(spec, stat
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_invalid_signature_extra_participant(spec, state):
|
||||
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
rng = random.Random(3030)
|
||||
random_participant = rng.choice(committee_indices)
|
||||
|
||||
@ -134,7 +133,7 @@ def test_invalid_signature_extra_participant(spec, state):
|
||||
@with_presets([MINIMAL], reason="to create nonduplicate committee")
|
||||
@spec_state_test
|
||||
def test_sync_committee_rewards_nonduplicate_committee(spec, state):
|
||||
committee_indices = get_committee_indices(spec, state, duplicates=False)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
committee_size = len(committee_indices)
|
||||
committee_bits = [True] * committee_size
|
||||
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
|
||||
@ -150,7 +149,7 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state):
|
||||
@with_presets([MAINNET], reason="to create duplicate committee")
|
||||
@spec_state_test
|
||||
def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state):
|
||||
committee_indices = get_committee_indices(spec, state, duplicates=True)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
committee_size = len(committee_indices)
|
||||
committee_bits = [False] * committee_size
|
||||
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
|
||||
@ -166,7 +165,7 @@ def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state
|
||||
@with_presets([MAINNET], reason="to create duplicate committee")
|
||||
@spec_state_test
|
||||
def test_sync_committee_rewards_duplicate_committee_half_participation(spec, state):
|
||||
committee_indices = get_committee_indices(spec, state, duplicates=True)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
committee_size = len(committee_indices)
|
||||
committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2)
|
||||
assert len(committee_bits) == committee_size
|
||||
@ -183,7 +182,7 @@ def test_sync_committee_rewards_duplicate_committee_half_participation(spec, sta
|
||||
@with_presets([MAINNET], reason="to create duplicate committee")
|
||||
@spec_state_test
|
||||
def test_sync_committee_rewards_duplicate_committee_full_participation(spec, state):
|
||||
committee_indices = get_committee_indices(spec, state, duplicates=True)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
committee_size = len(committee_indices)
|
||||
committee_bits = [True] * committee_size
|
||||
active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state)))
|
||||
@ -199,7 +198,7 @@ def test_sync_committee_rewards_duplicate_committee_full_participation(spec, sta
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_sync_committee_rewards_not_full_participants(spec, state):
|
||||
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
rng = random.Random(1010)
|
||||
committee_bits = [rng.choice([True, False]) for _ in committee_indices]
|
||||
|
||||
@ -210,7 +209,7 @@ def test_sync_committee_rewards_not_full_participants(spec, state):
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_sync_committee_rewards_empty_participants(spec, state):
|
||||
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
committee_bits = [False for _ in committee_indices]
|
||||
|
||||
yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits)
|
||||
@ -220,7 +219,7 @@ def test_sync_committee_rewards_empty_participants(spec, state):
|
||||
@spec_state_test
|
||||
@always_bls
|
||||
def test_invalid_signature_past_block(spec, state):
|
||||
committee_indices = compute_committee_indices(spec, state, state.current_sync_committee)
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
|
||||
for _ in range(2):
|
||||
# NOTE: need to transition twice to move beyond the degenerate case at genesis
|
||||
|
@ -2,10 +2,19 @@ import random
|
||||
from eth2spec.test.helpers.constants import (
|
||||
MAINNET, MINIMAL,
|
||||
)
|
||||
from eth2spec.test.helpers.random import (
|
||||
randomize_state,
|
||||
)
|
||||
from eth2spec.test.helpers.state import (
|
||||
has_active_balance_differential,
|
||||
)
|
||||
from eth2spec.test.helpers.sync_committee import (
|
||||
get_committee_indices,
|
||||
compute_committee_indices,
|
||||
run_successful_sync_committee_test,
|
||||
)
|
||||
from eth2spec.test.helpers.voluntary_exits import (
|
||||
get_unslashed_exited_validators,
|
||||
)
|
||||
from eth2spec.test.context import (
|
||||
with_altair_and_later,
|
||||
spec_state_test,
|
||||
@ -18,8 +27,8 @@ from eth2spec.test.context import (
|
||||
)
|
||||
|
||||
|
||||
def _test_harness_for_randomized_test_case(spec, state, duplicates=False, participation_fn=None):
|
||||
committee_indices = get_committee_indices(spec, state, duplicates=duplicates)
|
||||
def _test_harness_for_randomized_test_case(spec, state, expect_duplicates=False, participation_fn=None):
|
||||
committee_indices = compute_committee_indices(spec, state)
|
||||
|
||||
if participation_fn:
|
||||
participating_indices = participation_fn(committee_indices)
|
||||
@ -28,7 +37,7 @@ def _test_harness_for_randomized_test_case(spec, state, duplicates=False, partic
|
||||
|
||||
committee_bits = [index in participating_indices for index in committee_indices]
|
||||
committee_size = len(committee_indices)
|
||||
if duplicates:
|
||||
if expect_duplicates:
|
||||
assert committee_size > len(set(committee_indices))
|
||||
else:
|
||||
assert committee_size == len(set(committee_indices))
|
||||
@ -44,7 +53,7 @@ def test_random_only_one_participant_with_duplicates(spec, state):
|
||||
yield from _test_harness_for_randomized_test_case(
|
||||
spec,
|
||||
state,
|
||||
duplicates=True,
|
||||
expect_duplicates=True,
|
||||
participation_fn=lambda comm: [rng.choice(comm)],
|
||||
)
|
||||
|
||||
@ -57,7 +66,7 @@ def test_random_low_participation_with_duplicates(spec, state):
|
||||
yield from _test_harness_for_randomized_test_case(
|
||||
spec,
|
||||
state,
|
||||
duplicates=True,
|
||||
expect_duplicates=True,
|
||||
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.25)),
|
||||
)
|
||||
|
||||
@ -70,7 +79,7 @@ def test_random_high_participation_with_duplicates(spec, state):
|
||||
yield from _test_harness_for_randomized_test_case(
|
||||
spec,
|
||||
state,
|
||||
duplicates=True,
|
||||
expect_duplicates=True,
|
||||
participation_fn=lambda comm: rng.sample(comm, int(len(comm) * 0.75)),
|
||||
)
|
||||
|
||||
@ -83,7 +92,7 @@ def test_random_all_but_one_participating_with_duplicates(spec, state):
|
||||
yield from _test_harness_for_randomized_test_case(
|
||||
spec,
|
||||
state,
|
||||
duplicates=True,
|
||||
expect_duplicates=True,
|
||||
participation_fn=lambda comm: rng.sample(comm, len(comm) - 1),
|
||||
)
|
||||
|
||||
@ -98,7 +107,25 @@ def test_random_misc_balances_and_half_participation_with_duplicates(spec, state
|
||||
yield from _test_harness_for_randomized_test_case(
|
||||
spec,
|
||||
state,
|
||||
duplicates=True,
|
||||
expect_duplicates=True,
|
||||
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
|
||||
)
|
||||
|
||||
|
||||
@with_altair_and_later
|
||||
@with_presets([MAINNET], reason="to create duplicate committee")
|
||||
@spec_state_test
|
||||
@single_phase
|
||||
def test_random_with_exits_with_duplicates(spec, state):
|
||||
rng = random.Random(1402)
|
||||
randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0)
|
||||
target_validators = get_unslashed_exited_validators(spec, state)
|
||||
assert len(target_validators) != 0
|
||||
assert has_active_balance_differential(spec, state)
|
||||
yield from _test_harness_for_randomized_test_case(
|
||||
spec,
|
||||
state,
|
||||
expect_duplicates=True,
|
||||
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
|
||||
)
|
||||
|
||||
@ -163,3 +190,20 @@ def test_random_misc_balances_and_half_participation_without_duplicates(spec, st
|
||||
state,
|
||||
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
|
||||
)
|
||||
|
||||
|
||||
@with_altair_and_later
|
||||
@with_presets([MINIMAL], reason="to create nonduplicate committee")
|
||||
@spec_state_test
|
||||
@single_phase
|
||||
def test_random_with_exits_without_duplicates(spec, state):
|
||||
rng = random.Random(1502)
|
||||
randomize_state(spec, state, rng=rng, exit_fraction=0.1, slash_fraction=0.0)
|
||||
target_validators = get_unslashed_exited_validators(spec, state)
|
||||
assert len(target_validators) != 0
|
||||
assert has_active_balance_differential(spec, state)
|
||||
yield from _test_harness_for_randomized_test_case(
|
||||
spec,
|
||||
state,
|
||||
participation_fn=lambda comm: rng.sample(comm, len(comm) // 2),
|
||||
)
|
||||
|
@ -128,3 +128,34 @@ def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fra
|
||||
exit_random_validators(spec, state, rng, fraction=exit_fraction)
|
||||
slash_random_validators(spec, state, rng, fraction=slash_fraction)
|
||||
randomize_attestation_participation(spec, state, rng)
|
||||
|
||||
|
||||
def patch_state_to_non_leaking(spec, state):
|
||||
"""
|
||||
This function performs an irregular state transition so that:
|
||||
1. the current justified checkpoint references the previous epoch
|
||||
2. the previous justified checkpoint references the epoch before previous
|
||||
3. the finalized checkpoint matches the previous justified checkpoint
|
||||
|
||||
The effects of this function are intended to offset randomization side effects
|
||||
performed by other functionality in this module so that if the ``state`` was leaking,
|
||||
then the ``state`` is not leaking after.
|
||||
"""
|
||||
state.justification_bits[0] = True
|
||||
state.justification_bits[1] = True
|
||||
previous_epoch = spec.get_previous_epoch(state)
|
||||
previous_root = spec.get_block_root(state, previous_epoch)
|
||||
previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1))
|
||||
previous_previous_root = spec.get_block_root(state, previous_previous_epoch)
|
||||
state.previous_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=previous_previous_epoch,
|
||||
root=previous_previous_root,
|
||||
)
|
||||
state.current_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=previous_epoch,
|
||||
root=previous_root,
|
||||
)
|
||||
state.finalized_checkpoint = spec.Checkpoint(
|
||||
epoch=previous_previous_epoch,
|
||||
root=previous_previous_root,
|
||||
)
|
||||
|
@ -255,7 +255,19 @@ def run_get_inactivity_penalty_deltas(spec, state):
|
||||
else:
|
||||
assert penalties[index] > base_penalty
|
||||
else:
|
||||
assert penalties[index] == 0
|
||||
if not is_post_altair(spec):
|
||||
assert penalties[index] == 0
|
||||
continue
|
||||
else:
|
||||
# post altair, this penalty is derived from the inactivity score
|
||||
# regardless if the state is leaking or not...
|
||||
if index in matching_attesting_indices:
|
||||
assert penalties[index] == 0
|
||||
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
|
||||
assert penalties[index] == penalty_numerator // penalty_denominator
|
||||
|
||||
|
||||
def transition_state_to_leak(spec, state, epochs=None):
|
||||
|
@ -1,6 +1,6 @@
|
||||
from eth2spec.test.context import expect_assertion_error, is_post_altair
|
||||
from eth2spec.test.helpers.block import apply_empty_block, sign_block, transition_unsigned_block
|
||||
from eth2spec.test.helpers.voluntary_exits import get_exited_validators
|
||||
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
|
||||
|
||||
|
||||
def get_balance(state, index):
|
||||
@ -142,7 +142,7 @@ def ensure_state_has_validators_across_lifecycle(spec, state):
|
||||
for each of the following lifecycle states:
|
||||
1. Pending / deposited
|
||||
2. Active
|
||||
3. Exited
|
||||
3. Exited (but not slashed)
|
||||
4. Slashed
|
||||
"""
|
||||
has_pending = any(filter(spec.is_eligible_for_activation_queue, state.validators))
|
||||
@ -150,8 +150,18 @@ def ensure_state_has_validators_across_lifecycle(spec, state):
|
||||
current_epoch = spec.get_current_epoch(state)
|
||||
has_active = any(filter(lambda v: spec.is_active_validator(v, current_epoch), state.validators))
|
||||
|
||||
has_exited = any(get_exited_validators(spec, state))
|
||||
has_exited = any(get_unslashed_exited_validators(spec, state))
|
||||
|
||||
has_slashed = any(filter(lambda v: v.slashed, state.validators))
|
||||
|
||||
return has_pending and has_active and has_exited and has_slashed
|
||||
|
||||
|
||||
def has_active_balance_differential(spec, state):
|
||||
"""
|
||||
Ensure there is a difference between the total balance of
|
||||
all _active_ validators and _all_ validators.
|
||||
"""
|
||||
active_balance = spec.get_total_active_balance(state)
|
||||
total_balance = spec.get_total_balance(state, set(range(len(state.validators))))
|
||||
return active_balance // spec.EFFECTIVE_BALANCE_INCREMENT != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT
|
||||
|
@ -9,7 +9,6 @@ from eth2spec.test.helpers.block import (
|
||||
)
|
||||
from eth2spec.test.helpers.block_processing import run_block_processing_to
|
||||
from eth2spec.utils import bls
|
||||
from eth2spec.utils.hash_function import hash
|
||||
|
||||
|
||||
def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None, domain_type=None):
|
||||
@ -75,10 +74,12 @@ def compute_sync_committee_proposer_reward(spec, state, committee_indices, commi
|
||||
return spec.Gwei(participant_reward * participant_number)
|
||||
|
||||
|
||||
def compute_committee_indices(spec, state, committee):
|
||||
def compute_committee_indices(spec, state, committee=None):
|
||||
"""
|
||||
Given a ``committee``, calculate and return the related indices
|
||||
"""
|
||||
if committee is None:
|
||||
committee = state.current_sync_committee
|
||||
all_pubkeys = [v.pubkey for v in state.validators]
|
||||
return [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys]
|
||||
|
||||
@ -153,6 +154,7 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in
|
||||
state,
|
||||
block.slot - 1,
|
||||
[index for index, bit in zip(committee_indices, committee_bits) if bit],
|
||||
block_root=block.parent_root,
|
||||
)
|
||||
)
|
||||
return block
|
||||
@ -161,23 +163,3 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in
|
||||
def run_successful_sync_committee_test(spec, state, committee_indices, committee_bits):
|
||||
block = _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits)
|
||||
yield from run_sync_committee_processing(spec, state, block)
|
||||
|
||||
|
||||
def get_committee_indices(spec, state, duplicates=False):
|
||||
"""
|
||||
This utility function allows the caller to ensure there are or are not
|
||||
duplicate validator indices in the returned committee based on
|
||||
the boolean ``duplicates``.
|
||||
"""
|
||||
state = state.copy()
|
||||
current_epoch = spec.get_current_epoch(state)
|
||||
randao_index = (current_epoch + 1) % spec.EPOCHS_PER_HISTORICAL_VECTOR
|
||||
while True:
|
||||
committee = spec.get_next_sync_committee_indices(state)
|
||||
if duplicates:
|
||||
if len(committee) != len(set(committee)):
|
||||
return committee
|
||||
else:
|
||||
if len(committee) == len(set(committee)):
|
||||
return committee
|
||||
state.randao_mixes[randao_index] = hash(state.randao_mixes[randao_index])
|
||||
|
@ -34,6 +34,13 @@ def get_exited_validators(spec, state):
|
||||
return [index for (index, validator) in enumerate(state.validators) if validator.exit_epoch <= current_epoch]
|
||||
|
||||
|
||||
def get_unslashed_exited_validators(spec, state):
|
||||
return [
|
||||
index for index in get_exited_validators(spec, state)
|
||||
if not state.validators[index].slashed
|
||||
]
|
||||
|
||||
|
||||
def exit_validators(spec, state, validator_count, rng=None):
|
||||
if rng is None:
|
||||
rng = Random(1337)
|
||||
|
@ -1,8 +1,10 @@
|
||||
from random import Random
|
||||
from eth2spec.test.context import is_post_altair, spec_state_test, with_all_phases
|
||||
from eth2spec.test.helpers.epoch_processing import (
|
||||
run_epoch_processing_with,
|
||||
)
|
||||
from eth2spec.test.helpers.state import transition_to
|
||||
from eth2spec.test.helpers.state import transition_to, next_epoch_via_block, next_slot
|
||||
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
|
||||
|
||||
|
||||
def run_process_just_and_fin(spec, state):
|
||||
@ -300,3 +302,76 @@ def test_12_ok_support_messed_target(spec, state):
|
||||
@spec_state_test
|
||||
def test_12_poor_support(spec, state):
|
||||
yield from finalize_on_12(spec, state, 3, False, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_balance_threshold_with_exited_validators(spec, state):
|
||||
"""
|
||||
This test exercises a very specific failure mode where
|
||||
exited validators are incorrectly included in the total active balance
|
||||
when weighing justification.
|
||||
"""
|
||||
rng = Random(133333)
|
||||
# move past genesis conditions
|
||||
for _ in range(3):
|
||||
next_epoch_via_block(spec, state)
|
||||
|
||||
# mock attestation helper requires last slot of epoch
|
||||
for _ in range(spec.SLOTS_PER_EPOCH - 1):
|
||||
next_slot(spec, state)
|
||||
|
||||
# Step 1: Exit ~1/2 vals in current epoch
|
||||
epoch = spec.get_current_epoch(state)
|
||||
for index in spec.get_active_validator_indices(state, epoch):
|
||||
if rng.choice([True, False]):
|
||||
continue
|
||||
|
||||
validator = state.validators[index]
|
||||
validator.exit_epoch = epoch
|
||||
validator.withdrawable_epoch = epoch + 1
|
||||
validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
|
||||
|
||||
exited_validators = get_unslashed_exited_validators(spec, state)
|
||||
assert len(exited_validators) != 0
|
||||
|
||||
source = state.current_justified_checkpoint
|
||||
target = spec.Checkpoint(
|
||||
epoch=epoch,
|
||||
root=spec.get_block_root(state, epoch)
|
||||
)
|
||||
add_mock_attestations(
|
||||
spec,
|
||||
state,
|
||||
epoch,
|
||||
source,
|
||||
target,
|
||||
sufficient_support=False,
|
||||
)
|
||||
|
||||
if not is_post_altair(spec):
|
||||
current_attestations = spec.get_matching_target_attestations(state, epoch)
|
||||
total_active_balance = spec.get_total_active_balance(state)
|
||||
current_target_balance = spec.get_attesting_balance(state, current_attestations)
|
||||
# Check we will not justify the current checkpoint
|
||||
does_justify = current_target_balance * 3 >= total_active_balance * 2
|
||||
assert not does_justify
|
||||
# Ensure we would have justified the current checkpoint w/ the exited validators
|
||||
current_exited_balance = spec.get_total_balance(state, exited_validators)
|
||||
does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2
|
||||
assert does_justify
|
||||
else:
|
||||
current_indices = spec.get_unslashed_participating_indices(state, spec.TIMELY_TARGET_FLAG_INDEX, epoch)
|
||||
total_active_balance = spec.get_total_active_balance(state)
|
||||
current_target_balance = spec.get_total_balance(state, current_indices)
|
||||
# Check we will not justify the current checkpoint
|
||||
does_justify = current_target_balance * 3 >= total_active_balance * 2
|
||||
assert not does_justify
|
||||
# Ensure we would have justified the current checkpoint w/ the exited validators
|
||||
current_exited_balance = spec.get_total_balance(state, exited_validators)
|
||||
does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2
|
||||
assert does_justify
|
||||
|
||||
yield from run_process_just_and_fin(spec, state)
|
||||
|
||||
assert state.current_justified_checkpoint.epoch != epoch
|
||||
|
@ -1,7 +1,11 @@
|
||||
from random import Random
|
||||
from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair
|
||||
from eth2spec.test.helpers.epoch_processing import (
|
||||
run_epoch_processing_with, run_epoch_processing_to
|
||||
)
|
||||
from eth2spec.test.helpers.random import randomize_state
|
||||
from eth2spec.test.helpers.state import has_active_balance_differential
|
||||
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
|
||||
from eth2spec.test.helpers.state import next_epoch
|
||||
|
||||
|
||||
@ -22,6 +26,9 @@ def slash_validators(spec, state, indices, out_epochs):
|
||||
spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR
|
||||
] = total_slashed_balance
|
||||
|
||||
# verify some slashings happened...
|
||||
assert total_slashed_balance != 0
|
||||
|
||||
|
||||
def get_slashing_multiplier(spec):
|
||||
if is_post_altair(spec):
|
||||
@ -30,9 +37,7 @@ def get_slashing_multiplier(spec):
|
||||
return spec.PROPORTIONAL_SLASHING_MULTIPLIER
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_max_penalties(spec, state):
|
||||
def _setup_process_slashings_test(spec, state, not_slashable_set=set()):
|
||||
# Slashed count to ensure that enough validators are slashed to induce maximum penalties
|
||||
slashed_count = min(
|
||||
(len(state.validators) // get_slashing_multiplier(spec)) + 1,
|
||||
@ -41,14 +46,23 @@ def test_max_penalties(spec, state):
|
||||
)
|
||||
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
|
||||
|
||||
slashed_indices = list(range(slashed_count))
|
||||
slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count)
|
||||
eligible_indices = set(range(slashed_count))
|
||||
slashed_indices = eligible_indices.difference(not_slashable_set)
|
||||
slash_validators(spec, state, sorted(slashed_indices), [out_epoch] * slashed_count)
|
||||
|
||||
total_balance = spec.get_total_active_balance(state)
|
||||
total_penalties = sum(state.slashings)
|
||||
|
||||
assert total_balance // get_slashing_multiplier(spec) <= total_penalties
|
||||
|
||||
return slashed_indices
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_max_penalties(spec, state):
|
||||
slashed_indices = _setup_process_slashings_test(spec, state)
|
||||
|
||||
yield from run_process_slashings(spec, state)
|
||||
|
||||
for i in slashed_indices:
|
||||
@ -171,3 +185,28 @@ def test_scaled_penalties(spec, state):
|
||||
* spec.EFFECTIVE_BALANCE_INCREMENT
|
||||
)
|
||||
assert state.balances[i] == pre_slash_balances[i] - expected_penalty
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_slashings_with_random_state(spec, state):
|
||||
rng = Random(9998)
|
||||
randomize_state(spec, state, rng)
|
||||
|
||||
pre_balances = state.balances.copy()
|
||||
|
||||
target_validators = get_unslashed_exited_validators(spec, state)
|
||||
assert len(target_validators) != 0
|
||||
assert has_active_balance_differential(spec, state)
|
||||
|
||||
slashed_indices = _setup_process_slashings_test(spec, state, not_slashable_set=target_validators)
|
||||
|
||||
# ensure no accidental slashings of protected set...
|
||||
current_target_validators = get_unslashed_exited_validators(spec, state)
|
||||
assert len(current_target_validators) != 0
|
||||
assert current_target_validators == target_validators
|
||||
|
||||
yield from run_process_slashings(spec, state)
|
||||
|
||||
for i in slashed_indices:
|
||||
assert state.balances[i] < pre_balances[i]
|
||||
|
@ -9,6 +9,9 @@ from eth2spec.test.context import (
|
||||
low_balances, misc_balances,
|
||||
)
|
||||
import eth2spec.test.helpers.rewards as rewards_helpers
|
||||
from eth2spec.test.helpers.random import randomize_state, patch_state_to_non_leaking
|
||||
from eth2spec.test.helpers.state import has_active_balance_differential
|
||||
from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@ -35,6 +38,21 @@ def test_full_random_3(spec, state):
|
||||
yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(4040))
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_full_random_4(spec, state):
|
||||
"""
|
||||
Ensure a rewards test with some exited (but not slashed) validators.
|
||||
"""
|
||||
rng = Random(5050)
|
||||
randomize_state(spec, state, rng)
|
||||
assert spec.is_in_inactivity_leak(state)
|
||||
target_validators = get_unslashed_exited_validators(spec, state)
|
||||
assert len(target_validators) != 0
|
||||
assert has_active_balance_differential(spec, state)
|
||||
yield from rewards_helpers.run_deltas(spec, state)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
|
||||
@spec_test
|
||||
@ -57,3 +75,17 @@ def test_full_random_low_balances_1(spec, state):
|
||||
@single_phase
|
||||
def test_full_random_misc_balances(spec, state):
|
||||
yield from rewards_helpers.run_test_full_random(spec, state, rng=Random(7070))
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@spec_state_test
|
||||
def test_full_random_without_leak_0(spec, state):
|
||||
rng = Random(1010)
|
||||
randomize_state(spec, state, rng)
|
||||
assert spec.is_in_inactivity_leak(state)
|
||||
patch_state_to_non_leaking(spec, state)
|
||||
assert not spec.is_in_inactivity_leak(state)
|
||||
target_validators = get_unslashed_exited_validators(spec, state)
|
||||
assert len(target_validators) != 0
|
||||
assert has_active_balance_differential(spec, state)
|
||||
yield from rewards_helpers.run_deltas(spec, state)
|
||||
|
@ -17,6 +17,7 @@ from eth2spec.test.helpers.inactivity_scores import (
|
||||
)
|
||||
from eth2spec.test.helpers.random import (
|
||||
randomize_state as randomize_state_helper,
|
||||
patch_state_to_non_leaking,
|
||||
)
|
||||
from eth2spec.test.helpers.state import (
|
||||
next_slot,
|
||||
@ -274,23 +275,7 @@ def _randomized_scenario_setup(state_randomizer):
|
||||
may not reflect this condition with prior (arbitrary) mutations,
|
||||
so this mutator addresses that fact.
|
||||
"""
|
||||
state.justification_bits = (True, True, True, True)
|
||||
previous_epoch = spec.get_previous_epoch(state)
|
||||
previous_root = spec.get_block_root(state, previous_epoch)
|
||||
previous_previous_epoch = max(spec.GENESIS_EPOCH, spec.Epoch(previous_epoch - 1))
|
||||
previous_previous_root = spec.get_block_root(state, previous_previous_epoch)
|
||||
state.previous_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=previous_previous_epoch,
|
||||
root=previous_previous_root,
|
||||
)
|
||||
state.current_justified_checkpoint = spec.Checkpoint(
|
||||
epoch=previous_epoch,
|
||||
root=previous_root,
|
||||
)
|
||||
state.finalized_checkpoint = spec.Checkpoint(
|
||||
epoch=previous_previous_epoch,
|
||||
root=previous_previous_root,
|
||||
)
|
||||
patch_state_to_non_leaking(spec, state)
|
||||
|
||||
return (
|
||||
# NOTE: the block randomization function assumes at least 1 shard committee period
|
||||
|
Loading…
x
Reference in New Issue
Block a user