Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test double proposer slashings and exits #1781

Merged
merged 4 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
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

Expand All @@ -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)
Expand Down
132 changes: 100 additions & 32 deletions tests/core/pyspec/eth2spec/test/sanity/test_blocks.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

djrtwo marked this conversation as resolved.
Show resolved Hide resolved

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]
Expand All @@ -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
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
# of duplicate slashings and overlaps of slashed attestations within the same block


@with_all_phases
Expand Down Expand Up @@ -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
Expand All @@ -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

djrtwo marked this conversation as resolved.
Show resolved Hide resolved

@with_all_phases
@spec_state_test
def test_balance_driven_status_transitions(spec, state):
Expand Down