From c17a95a175d7ef149572e36b823eda28163f707b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 14 Oct 2020 17:33:14 -0600 Subject: [PATCH] add note about how slashings and exits can interact. add test --- specs/phase0/validator.md | 4 ++ .../eth2spec/test/helpers/voluntary_exits.py | 15 ++++++ .../test/phase0/sanity/test_blocks.py | 17 +----- .../phase0/sanity/test_multi_operations.py | 53 +++++++++++++++++++ 4 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index be9a16755..82bd4cf6e 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -358,6 +358,10 @@ The `proof` for each deposit must be constructed against the deposit root contai Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](./beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](./beacon-chain.md#voluntary-exits). +*Note*: If a slashing for a validator is included in the same block as a +voluntary exit, the voluntary exit will fail and cause the block to be invalid +due to the slashing being processed first. Implementers must take heed of this +operation interaction when packing blocks. #### Packaging into a `SignedBeaconBlock` diff --git a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py index 55310ef7d..28232cc23 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py @@ -1,4 +1,19 @@ from eth2spec.utils import bls +from eth2spec.test.helpers.keys import privkeys + + +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] def sign_voluntary_exit(spec, state, voluntary_exit, privkey): diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 721d68add..dc56965ac 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -9,7 +9,7 @@ from eth2spec.test.helpers.block import ( sign_block, transition_unsigned_block, ) -from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.keys import pubkeys from eth2spec.test.helpers.attester_slashings import ( get_valid_attester_slashing_by_indices, get_valid_attester_slashing, @@ -18,6 +18,7 @@ from eth2spec.test.helpers.attester_slashings import ( from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.context import ( @@ -794,20 +795,6 @@ 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 SHARD_COMMITTEE_PERIOD slots ago, # exceeding the minimal-config randao mixes memory size. # Applies to all voluntary-exit sanity block tests. diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py new file mode 100644 index 000000000..1e1ed42f5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py @@ -0,0 +1,53 @@ +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases, +) + + +def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=slash_index, signed_1=True, signed_2=True) + signed_exit = prepare_signed_exits(spec, state, [exit_index])[0] + + block.body.proposer_slashings.append(proposer_slashing) + block.body.voluntary_exits.append(signed_exit) + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid)) + + yield 'blocks', [signed_block] + + if valid: + yield 'post', state + else: + yield 'post', None + + +@with_all_phases +@spec_state_test +def test_slash_and_exit_same_index(spec, state): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + run_slash_and_exit(spec, state, validator_index, validator_index, valid=False) + + +@with_all_phases +@spec_state_test +def test_slash_and_exit_diff_index(spec, state): + slash_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + exit_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-2] + run_slash_and_exit(spec, state, slash_index, exit_index)