Merge pull request #1781 from ethereum/double-operations
test double proposer slashings and exits
This commit is contained in:
commit
197f1f5496
|
@ -1,22 +1,55 @@
|
||||||
from eth2spec.test.helpers.block_header import sign_block_header
|
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
|
||||||
|
from eth2spec.test.helpers.state import get_balance
|
||||||
|
|
||||||
|
|
||||||
def get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False):
|
def check_proposer_slashing_effect(spec, pre_state, state, slashed_index):
|
||||||
current_epoch = spec.get_current_epoch(state)
|
slashed_validator = state.validators[slashed_index]
|
||||||
validator_index = spec.get_active_validator_indices(state, current_epoch)[-1]
|
assert slashed_validator.slashed
|
||||||
privkey = pubkey_to_privkey[state.validators[validator_index].pubkey]
|
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
proposer_index = spec.get_beacon_proposer_index(state)
|
||||||
|
slash_penalty = state.validators[slashed_index].effective_balance // spec.MIN_SLASHING_PENALTY_QUOTIENT
|
||||||
|
whistleblower_reward = state.validators[slashed_index].effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT
|
||||||
|
if proposer_index != slashed_index:
|
||||||
|
# slashed validator lost initial slash penalty
|
||||||
|
assert (
|
||||||
|
get_balance(state, slashed_index)
|
||||||
|
== get_balance(pre_state, slashed_index) - slash_penalty
|
||||||
|
)
|
||||||
|
# block proposer gained whistleblower reward
|
||||||
|
# >= because proposer could have reported multiple
|
||||||
|
assert (
|
||||||
|
get_balance(state, proposer_index)
|
||||||
|
>= get_balance(pre_state, proposer_index) + whistleblower_reward
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# proposer reported themself so get penalty and reward
|
||||||
|
# >= because proposer could have reported multiple
|
||||||
|
assert (
|
||||||
|
get_balance(state, slashed_index)
|
||||||
|
>= get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32,
|
||||||
|
slashed_index=None, signed_1=False, signed_2=False):
|
||||||
|
if slashed_index is None:
|
||||||
|
current_epoch = spec.get_current_epoch(state)
|
||||||
|
slashed_index = spec.get_active_validator_indices(state, current_epoch)[-1]
|
||||||
|
privkey = pubkey_to_privkey[state.validators[slashed_index].pubkey]
|
||||||
slot = state.slot
|
slot = state.slot
|
||||||
|
|
||||||
header_1 = spec.BeaconBlockHeader(
|
header_1 = spec.BeaconBlockHeader(
|
||||||
slot=slot,
|
slot=slot,
|
||||||
proposer_index=validator_index,
|
proposer_index=slashed_index,
|
||||||
parent_root=b'\x33' * 32,
|
parent_root=b'\x33' * 32,
|
||||||
state_root=b'\x44' * 32,
|
state_root=b'\x44' * 32,
|
||||||
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)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
|
from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases
|
||||||
|
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||||
from eth2spec.test.helpers.block_header import sign_block_header
|
from eth2spec.test.helpers.block_header import sign_block_header
|
||||||
from eth2spec.test.helpers.keys import privkeys
|
from eth2spec.test.helpers.keys import privkeys
|
||||||
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
|
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
|
||||||
from eth2spec.test.helpers.state import get_balance, next_epoch
|
from eth2spec.test.helpers.state import next_epoch
|
||||||
|
|
||||||
|
|
||||||
def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True):
|
def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True):
|
||||||
|
@ -14,6 +15,8 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True)
|
||||||
If ``valid == False``, run expecting ``AssertionError``
|
If ``valid == False``, run expecting ``AssertionError``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
pre_state = state.copy()
|
||||||
|
|
||||||
yield 'pre', state
|
yield 'pre', state
|
||||||
yield 'proposer_slashing', proposer_slashing
|
yield 'proposer_slashing', proposer_slashing
|
||||||
|
|
||||||
|
@ -22,25 +25,31 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True)
|
||||||
yield 'post', None
|
yield 'post', None
|
||||||
return
|
return
|
||||||
|
|
||||||
proposer_index = proposer_slashing.signed_header_1.message.proposer_index
|
|
||||||
pre_proposer_balance = get_balance(state, proposer_index)
|
|
||||||
|
|
||||||
spec.process_proposer_slashing(state, proposer_slashing)
|
spec.process_proposer_slashing(state, proposer_slashing)
|
||||||
yield 'post', state
|
yield 'post', state
|
||||||
|
|
||||||
# check if slashed
|
slashed_proposer_index = proposer_slashing.signed_header_1.message.proposer_index
|
||||||
slashed_validator = state.validators[proposer_index]
|
check_proposer_slashing_effect(spec, pre_state, state, slashed_proposer_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, proposer_index) < pre_proposer_balance
|
|
||||||
|
|
||||||
|
|
||||||
@with_all_phases
|
@with_all_phases
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_success(spec, state):
|
def test_success(spec, state):
|
||||||
|
# Get proposer for next slot
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
proposer_index = block.proposer_index
|
||||||
|
|
||||||
|
# Create slashing for same proposer
|
||||||
|
proposer_slashing = get_valid_proposer_slashing(spec, state,
|
||||||
|
slashed_index=proposer_index,
|
||||||
|
signed_1=True, signed_2=True)
|
||||||
|
|
||||||
|
yield from run_proposer_slashing_processing(spec, state, proposer_slashing)
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
def test_success_slashed_and_proposer_index_the_same(spec, state):
|
||||||
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)
|
||||||
|
|
||||||
yield from run_proposer_slashing_processing(spec, state, proposer_slashing)
|
yield from run_proposer_slashing_processing(spec, state, proposer_slashing)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -7,7 +5,7 @@ from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_e
|
||||||
transition_unsigned_block
|
transition_unsigned_block
|
||||||
from eth2spec.test.helpers.keys import privkeys, pubkeys
|
from eth2spec.test.helpers.keys import privkeys, pubkeys
|
||||||
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants
|
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, get_indexed_attestation_participants
|
||||||
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
|
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
|
||||||
from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations
|
from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations
|
||||||
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
|
||||||
|
|
||||||
|
@ -231,11 +229,11 @@ 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
|
slashed_index = proposer_slashing.signed_header_1.message.proposer_index
|
||||||
|
|
||||||
assert not state.validators[validator_index].slashed
|
assert not state.validators[slashed_index].slashed
|
||||||
|
|
||||||
yield 'pre', state
|
yield 'pre', state
|
||||||
|
|
||||||
|
@ -250,7 +248,85 @@ def test_proposer_slashing(spec, state):
|
||||||
yield 'blocks', [signed_block]
|
yield 'blocks', [signed_block]
|
||||||
yield 'post', state
|
yield 'post', state
|
||||||
|
|
||||||
# check if slashed
|
check_proposer_slashing_effect(spec, pre_state, state, slashed_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)
|
||||||
|
slashed_index = proposer_slashing.signed_header_1.message.proposer_index
|
||||||
|
assert not state.validators[slashed_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):
|
||||||
|
slashed_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,
|
||||||
|
slashed_index=slashed_index,
|
||||||
|
signed_1=True, signed_2=True)
|
||||||
|
proposer_slashing_2 = get_valid_proposer_slashing(spec, state, random_root=b'\xbb' * 32,
|
||||||
|
slashed_index=slashed_index,
|
||||||
|
signed_1=True, signed_2=True)
|
||||||
|
assert not state.validators[slashed_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
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
def test_multiple_different_proposer_slashings_same_block(spec, state):
|
||||||
|
pre_state = state.copy()
|
||||||
|
|
||||||
|
num_slashings = 3
|
||||||
|
proposer_slashings = []
|
||||||
|
for i in range(num_slashings):
|
||||||
|
slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i]
|
||||||
|
assert not state.validators[slashed_index].slashed
|
||||||
|
|
||||||
|
proposer_slashing = get_valid_proposer_slashing(spec, state,
|
||||||
|
slashed_index=slashed_index,
|
||||||
|
signed_1=True, signed_2=True)
|
||||||
|
proposer_slashings.append(proposer_slashing)
|
||||||
|
|
||||||
|
yield 'pre', state
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add to state via block transition
|
||||||
|
#
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
block.body.proposer_slashings = proposer_slashings
|
||||||
|
|
||||||
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||||
|
|
||||||
|
yield 'blocks', [signed_block]
|
||||||
|
yield 'post', state
|
||||||
|
|
||||||
|
for proposer_slashing in proposer_slashings:
|
||||||
|
slashed_index = proposer_slashing.signed_header_1.message.proposer_index
|
||||||
|
check_proposer_slashing_effect(spec, pre_state, state, slashed_index)
|
||||||
|
|
||||||
|
|
||||||
|
def check_attester_slashing_effect(spec, pre_state, state, validator_index):
|
||||||
slashed_validator = state.validators[validator_index]
|
slashed_validator = state.validators[validator_index]
|
||||||
assert slashed_validator.slashed
|
assert slashed_validator.slashed
|
||||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
@ -258,12 +334,16 @@ def test_proposer_slashing(spec, state):
|
||||||
# lost whistleblower reward
|
# lost whistleblower reward
|
||||||
assert get_balance(state, validator_index) < get_balance(pre_state, validator_index)
|
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]
|
||||||
|
@ -283,19 +363,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 various 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
|
||||||
|
@ -448,35 +520,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
|
||||||
|
@ -491,6 +566,59 @@ 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_phases(['phase0'])
|
||||||
|
@spec_state_test
|
||||||
|
def test_multiple_different_validator_exits_same_block(spec, state):
|
||||||
|
validator_indices = [
|
||||||
|
spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i]
|
||||||
|
for i in range(3)
|
||||||
|
]
|
||||||
|
# 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_indices)
|
||||||
|
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)
|
||||||
|
|
||||||
|
for index in validator_indices:
|
||||||
|
assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
# Process within epoch transition
|
||||||
|
exit_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
|
||||||
|
signed_exit_block = state_transition_and_sign_block(spec, state, exit_block)
|
||||||
|
|
||||||
|
yield 'blocks', [signed_initiate_exit_block, signed_exit_block]
|
||||||
|
yield 'post', state
|
||||||
|
|
||||||
|
for index in validator_indices:
|
||||||
|
assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||||
|
|
||||||
|
|
||||||
@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):
|
||||||
|
|
Loading…
Reference in New Issue