diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index ac2ebcf9c..d753d55dd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -2,9 +2,11 @@ from eth2spec.test.helpers.block_header import sign_block_header from eth2spec.test.helpers.keys import pubkey_to_privkey -def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False): - current_epoch = spec.get_current_epoch(state) - validator_index = spec.get_active_validator_indices(state, current_epoch)[-1] +def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32, + validator_index=None, signed_1=False, signed_2=False): + if validator_index is None: + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[-1] privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] slot = state.slot @@ -16,7 +18,7 @@ def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False): body_root=b'\x55' * 32, ) header_2 = header_1.copy() - header_2.parent_root = b'\x99' * 32 + header_2.parent_root = random_root if signed_1: signed_header_1 = sign_block_header(spec, state, header_1, privkey) diff --git a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py index b6b671872..3347d4f8b 100644 --- a/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/sanity/test_blocks.py @@ -1,5 +1,3 @@ -from copy import deepcopy - from eth2spec.utils import bls from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block, next_slot, next_epoch @@ -228,7 +226,7 @@ def test_empty_epoch_transition_not_finalizing(spec, state): @spec_state_test def test_proposer_slashing(spec, state): # copy for later balance lookups. - pre_state = deepcopy(state) + pre_state = state.copy() proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) validator_index = proposer_slashing.signed_header_1.message.proposer_index @@ -256,11 +254,65 @@ def test_proposer_slashing(spec, state): assert get_balance(state, validator_index) < get_balance(pre_state, validator_index) +@with_all_phases +@spec_state_test +def test_double_same_proposer_slashings_same_block(spec, state): + proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + validator_index = proposer_slashing.signed_header_1.message.proposer_index + assert not state.validators[validator_index].slashed + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + block.body.proposer_slashings = [proposer_slashing, proposer_slashing] + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield 'blocks', [signed_block] + yield 'post', None + + +@with_all_phases +@spec_state_test +def test_double_similar_proposer_slashings_same_block(spec, state): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + + # Same validator, but different slashable offences in the same block + proposer_slashing_1 = get_valid_proposer_slashing(spec, state, random_root=b'\xaa' * 32, + validator_index=validator_index, + signed_1=True, signed_2=True) + proposer_slashing_2 = get_valid_proposer_slashing(spec, state, random_root=b'\xbb' * 32, + validator_index=validator_index, + signed_1=True, signed_2=True) + assert not state.validators[validator_index].slashed + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + block.body.proposer_slashings = [proposer_slashing_1, proposer_slashing_2] + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield 'blocks', [signed_block] + yield 'post', None + + +def check_attester_slashing_effect(spec, pre_state, state, validator_index): + slashed_validator = state.validators[validator_index] + assert slashed_validator.slashed + assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH + # lost whistleblower reward + assert get_balance(state, validator_index) < get_balance(pre_state, validator_index) + + proposer_index = spec.get_beacon_proposer_index(state) + # gained whistleblower reward + assert get_balance(state, proposer_index) > get_balance(pre_state, proposer_index) + + @with_all_phases @spec_state_test def test_attester_slashing(spec, state): # copy for later balance lookups. - pre_state = deepcopy(state) + pre_state = state.copy() attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) validator_index = get_indexed_attestation_participants(spec, attester_slashing.attestation_1)[0] @@ -280,19 +332,11 @@ def test_attester_slashing(spec, state): yield 'blocks', [signed_block] yield 'post', state - slashed_validator = state.validators[validator_index] - assert slashed_validator.slashed - assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH - assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH - # lost whistleblower reward - assert get_balance(state, validator_index) < get_balance(pre_state, validator_index) + check_attester_slashing_effect(spec, pre_state, state, validator_index) - proposer_index = spec.get_beacon_proposer_index(state) - # gained whistleblower reward - assert ( - get_balance(state, proposer_index) > - get_balance(pre_state, proposer_index) - ) +# TODO: currently mainnet limits attester-slashings per block to 1. +# When this is increased, it should be tested to cover varrious combinations +# of duplicate slashings and overlaps of slashed attestations within the same block @with_all_phases @@ -443,35 +487,38 @@ def test_attestation(spec, state): assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root +def prepare_signed_exits(spec, state, indices): + domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) + + def create_signed_exit(index): + exit = spec.VoluntaryExit( + epoch=spec.get_current_epoch(state), + validator_index=index, + ) + signing_root = spec.compute_signing_root(exit, domain) + return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root)) + + return [create_signed_exit(index) for index in indices] + + # In phase1 a committee is computed for PERSISTENT_COMMITTEE_PERIOD slots ago, # exceeding the minimal-config randao mixes memory size. +# Applies to all voluntary-exit sanity block tests. + @with_phases(['phase0']) @spec_state_test def test_voluntary_exit(spec, state): - validator_index = spec.get_active_validator_indices( - state, - spec.get_current_epoch(state) - )[-1] + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + signed_exits = prepare_signed_exits(spec, state, [validator_index]) yield 'pre', state - voluntary_exit = spec.VoluntaryExit( - epoch=spec.get_current_epoch(state), - validator_index=validator_index, - ) - domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) - signing_root = spec.compute_signing_root(voluntary_exit, domain) - signed_voluntary_exit = spec.SignedVoluntaryExit( - message=voluntary_exit, - signature=bls.Sign(privkeys[validator_index], signing_root) - ) - # Add to state via block transition initiate_exit_block = build_empty_block_for_next_slot(spec, state) - initiate_exit_block.body.voluntary_exits.append(signed_voluntary_exit) + initiate_exit_block.body.voluntary_exits = signed_exits signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block) assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH @@ -486,6 +533,27 @@ def test_voluntary_exit(spec, state): assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH +@with_phases(['phase0']) +@spec_state_test +def test_double_validator_exit_same_block(spec, state): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + + # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # Same index tries to exit twice, but should only be able to do so once. + signed_exits = prepare_signed_exits(spec, state, [validator_index, validator_index]) + yield 'pre', state + + # Add to state via block transition + initiate_exit_block = build_empty_block_for_next_slot(spec, state) + initiate_exit_block.body.voluntary_exits = signed_exits + signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block, expect_fail=True) + + yield 'blocks', [signed_initiate_exit_block] + yield 'post', None + + @with_all_phases @spec_state_test def test_balance_driven_status_transitions(spec, state):