test double proposer slashings and exits

This commit is contained in:
protolambda 2020-05-01 19:14:01 +02:00
parent 524b84df78
commit 45ad270f76
No known key found for this signature in database
GPG Key ID: EC89FDBB2B4C7623
2 changed files with 106 additions and 36 deletions

View File

@ -2,9 +2,11 @@ from eth2spec.test.helpers.block_header import sign_block_header
from eth2spec.test.helpers.keys import pubkey_to_privkey from eth2spec.test.helpers.keys import pubkey_to_privkey
def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False): def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32,
current_epoch = spec.get_current_epoch(state) validator_index=None, signed_1=False, signed_2=False):
validator_index = spec.get_active_validator_indices(state, current_epoch)[-1] 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] privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
slot = state.slot 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, body_root=b'\x55' * 32,
) )
header_2 = header_1.copy() header_2 = header_1.copy()
header_2.parent_root = b'\x99' * 32 header_2.parent_root = random_root
if signed_1: if signed_1:
signed_header_1 = sign_block_header(spec, state, header_1, privkey) signed_header_1 = sign_block_header(spec, state, header_1, privkey)

View File

@ -1,5 +1,3 @@
from copy import deepcopy
from eth2spec.utils import bls from eth2spec.utils import bls
from eth2spec.test.helpers.state import get_balance, state_transition_and_sign_block, next_slot, next_epoch 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 @spec_state_test
def test_proposer_slashing(spec, state): def test_proposer_slashing(spec, state):
# copy for later balance lookups. # 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) proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True)
validator_index = proposer_slashing.signed_header_1.message.proposer_index 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) 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 @with_all_phases
@spec_state_test @spec_state_test
def test_attester_slashing(spec, state): def test_attester_slashing(spec, state):
# copy for later balance lookups. # 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) 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] 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 'blocks', [signed_block]
yield 'post', state yield 'post', state
slashed_validator = state.validators[validator_index] check_attester_slashing_effect(spec, pre_state, state, 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) # TODO: currently mainnet limits attester-slashings per block to 1.
# gained whistleblower reward # When this is increased, it should be tested to cover varrious combinations
assert ( # of duplicate slashings and overlaps of slashed attestations within the same block
get_balance(state, proposer_index) >
get_balance(pre_state, proposer_index)
)
@with_all_phases @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 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, # In phase1 a committee is computed for PERSISTENT_COMMITTEE_PERIOD slots ago,
# exceeding the minimal-config randao mixes memory size. # exceeding the minimal-config randao mixes memory size.
# Applies to all voluntary-exit sanity block tests.
@with_phases(['phase0']) @with_phases(['phase0'])
@spec_state_test @spec_state_test
def test_voluntary_exit(spec, state): def test_voluntary_exit(spec, state):
validator_index = spec.get_active_validator_indices( validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
state,
spec.get_current_epoch(state)
)[-1]
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
signed_exits = prepare_signed_exits(spec, state, [validator_index])
yield 'pre', state 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 # Add to state via block transition
initiate_exit_block = build_empty_block_for_next_slot(spec, state) 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) 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 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 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 @with_all_phases
@spec_state_test @spec_state_test
def test_balance_driven_status_transitions(spec, state): def test_balance_driven_status_transitions(spec, state):