PR feedback. Rework `transition_to_next_epoch_and_append_blocks` a bit

This commit is contained in:
Hsiao-Wei Wang 2021-10-15 21:08:21 +08:00
parent a4e5d50660
commit 162711ea56
No known key found for this signature in database
GPG Key ID: 1111A8A81778319E
5 changed files with 108 additions and 71 deletions

View File

@ -21,7 +21,8 @@ from eth2spec.test.helpers.random import (
# #
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@with_presets([MINIMAL], reason="only test with non-full committee") @with_presets([MINIMAL],
reason="only test with enough validators such that at lease one exited index is not in sync committee")
def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state,
fork_epoch, fork_epoch,
spec, spec,
@ -33,7 +34,13 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state,
and are exiting but still active *after* the fork transition. and are exiting but still active *after* the fork transition.
""" """
exited_indices = exit_random_validators( exited_indices = exit_random_validators(
spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=10, forward=False) spec,
state,
rng=random.Random(5566),
fraction=0.25,
exit_epoch=10,
forward=False,
)
transition_until_fork(spec, state, fork_epoch) transition_until_fork(spec, state, fork_epoch)
@ -85,7 +92,13 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state,
and being exited and inactive *right after* the fork transition. and being exited and inactive *right after* the fork transition.
""" """
exited_indices = exit_random_validators( exited_indices = exit_random_validators(
spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=fork_epoch, forward=False) spec,
state,
rng=random.Random(5566),
fraction=0.25,
exit_epoch=fork_epoch,
forward=False,
)
transition_until_fork(spec, state, fork_epoch) transition_until_fork(spec, state, fork_epoch)
@ -136,11 +149,11 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos
""" """
transition_until_fork(spec, state, fork_epoch) transition_until_fork(spec, state, fork_epoch)
queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) deposited_indices = set_some_new_deposits(spec, state, rng=random.Random(5566))
assert spec.get_current_epoch(state) < fork_epoch assert spec.get_current_epoch(state) < fork_epoch
assert len(queuing_indices) > 0 assert len(deposited_indices) > 0
for validator_index in queuing_indices: for validator_index in deposited_indices:
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state)) assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
yield "pre", state yield "pre", state

View File

@ -4,7 +4,7 @@ from eth2spec.test.context import (
) )
from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import ( from eth2spec.test.helpers.fork_transition import (
OperetionType, OperationType,
run_transition_with_operation, run_transition_with_operation,
) )
@ -26,7 +26,7 @@ def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, s
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.PROPOSER_SLASHING, operation_type=OperationType.PROPOSER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
) )
@ -44,7 +44,7 @@ def test_transition_with_proposer_slashing_right_before_fork(state, fork_epoch,
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.PROPOSER_SLASHING, operation_type=OperationType.PROPOSER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
) )
@ -67,7 +67,7 @@ def test_transition_with_attester_slashing_right_after_fork(state, fork_epoch, s
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.ATTESTER_SLASHING, operation_type=OperationType.ATTESTER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
) )
@ -85,7 +85,7 @@ def test_transition_with_attester_slashing_right_before_fork(state, fork_epoch,
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.ATTESTER_SLASHING, operation_type=OperationType.ATTESTER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
) )
@ -107,7 +107,7 @@ def test_transition_with_deposit_right_after_fork(state, fork_epoch, spec, post_
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.DEPOSIT, operation_type=OperationType.DEPOSIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
) )
@ -124,7 +124,7 @@ def test_transition_with_deposit_right_before_fork(state, fork_epoch, spec, post
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.DEPOSIT, operation_type=OperationType.DEPOSIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
) )
@ -150,7 +150,7 @@ def test_transition_with_voluntary_exit_right_after_fork(state, fork_epoch, spec
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.VOLUNTARY_EXIT, operation_type=OperationType.VOLUNTARY_EXIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
) )
@ -171,6 +171,6 @@ def test_transition_with_voluntary_exit_right_before_fork(state, fork_epoch, spe
post_spec, post_spec,
pre_tag, pre_tag,
post_tag, post_tag,
operation_type=OperetionType.VOLUNTARY_EXIT, operation_type=OperationType.VOLUNTARY_EXIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
) )

View File

@ -7,7 +7,7 @@ from eth2spec.test.context import (
from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import ( from eth2spec.test.helpers.fork_transition import (
do_altair_fork, do_altair_fork,
state_transition_across_slots_with_ignoring_proposers, transition_to_next_epoch_and_append_blocks,
transition_until_fork, transition_until_fork,
) )
from eth2spec.test.helpers.random import ( from eth2spec.test.helpers.random import (
@ -16,7 +16,8 @@ from eth2spec.test.helpers.random import (
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1)
@with_presets([MINIMAL], reason="only test with non-full committee") @with_presets([MINIMAL],
reason="only test with enough validators such that at lease one exited index is not in sync committee")
def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state,
fork_epoch, fork_epoch,
spec, spec,
@ -52,13 +53,16 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state,
assert any(set(slashed_pubkeys).difference(list(state.current_sync_committee.pubkeys))) assert any(set(slashed_pubkeys).difference(list(state.current_sync_committee.pubkeys)))
# continue regular state transition with new spec into next epoch # continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
# since the proposer might have been slashed, here we only create blocks with non-slashed proposers # since the proposer might have been slashed, here we only create blocks with non-slashed proposers
blocks = [] blocks = []
blocks.extend([ transition_to_next_epoch_and_append_blocks(
post_tag(block) for block in post_spec,
state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) state,
]) post_tag,
blocks,
only_last_block=True,
ignoring_proposers=slashed_indices,
)
# check post state # check post state
for validator in state.validators: for validator in state.validators:

View File

@ -1,8 +1,7 @@
from enum import Enum, auto from enum import Enum, auto
import random
from eth2spec.test.helpers.attester_slashings import ( from eth2spec.test.helpers.attester_slashings import (
get_valid_attester_slashing, get_valid_attester_slashing_by_indices,
) )
from eth2spec.test.helpers.attestations import next_slots_with_attestations from eth2spec.test.helpers.attestations import next_slots_with_attestations
from eth2spec.test.helpers.block import ( from eth2spec.test.helpers.block import (
@ -26,7 +25,7 @@ from eth2spec.test.helpers.voluntary_exits import (
) )
class OperetionType(Enum): class OperationType(Enum):
PROPOSER_SLASHING = auto() PROPOSER_SLASHING = auto()
ATTESTER_SLASHING = auto() ATTESTER_SLASHING = auto()
DEPOSIT = auto() DEPOSIT = auto()
@ -104,7 +103,11 @@ def state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks
next_slot(spec, state) next_slot(spec, state)
def state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, ignoring_proposers): def state_transition_across_slots_with_ignoring_proposers(spec,
state,
to_slot,
ignoring_proposers,
only_last_block=False):
""" """
The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers` The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers`
and ensure that the result state was computed with a block with slot >= to_slot. and ensure that the result state was computed with a block with slot >= to_slot.
@ -113,6 +116,10 @@ def state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot,
found_valid = False found_valid = False
while state.slot < to_slot or not found_valid: while state.slot < to_slot or not found_valid:
if state.slot + 1 < to_slot and only_last_block:
next_slot(spec, state)
continue
future_state = state.copy() future_state = state.copy()
next_slot(spec, future_state) next_slot(spec, future_state)
proposer_index = spec.get_beacon_proposer_index(future_state) proposer_index = spec.get_beacon_proposer_index(future_state)
@ -144,20 +151,6 @@ def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operatio
return state, None return state, None
def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25):
"""
Set some valdiators' `exit_epoch` and `withdrawable_epoch`.
"""
selected_count = int(len(state.validators) * fraction)
selected_indices = rng.sample(range(len(state.validators)), selected_count)
for validator_index in selected_indices:
state.validators[validator_index].exit_epoch = exit_epoch
state.validators[validator_index].withdrawable_epoch = (
exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
)
return selected_indices
def transition_until_fork(spec, state, fork_epoch): def transition_until_fork(spec, state, fork_epoch):
to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1
transition_to(spec, state, to_slot) transition_to(spec, state, to_slot)
@ -168,7 +161,12 @@ def _transition_until_fork_minus_one(spec, state, fork_epoch):
transition_to(spec, state, to_slot) transition_to(spec, state, to_slot)
def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, only_last_block=False): def transition_to_next_epoch_and_append_blocks(spec,
state,
post_tag,
blocks,
only_last_block=False,
ignoring_proposers=None):
to_slot = spec.SLOTS_PER_EPOCH + state.slot to_slot = spec.SLOTS_PER_EPOCH + state.slot
if only_last_block: if only_last_block:
@ -176,9 +174,20 @@ def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, on
else: else:
block_filter = _all_blocks block_filter = _all_blocks
if ignoring_proposers is None:
result_blocks = state_transition_across_slots(spec, state, to_slot, block_filter=block_filter)
else:
result_blocks = state_transition_across_slots_with_ignoring_proposers(
spec,
state,
to_slot,
ignoring_proposers,
only_last_block=only_last_block,
)
blocks.extend([ blocks.extend([
post_tag(block) for block in post_tag(block) for block in
state_transition_across_slots(spec, state, to_slot, block_filter=block_filter) result_blocks
]) ])
@ -203,27 +212,34 @@ def run_transition_with_operation(state,
elif is_right_before_fork: elif is_right_before_fork:
_transition_until_fork_minus_one(spec, state, fork_epoch) _transition_until_fork_minus_one(spec, state, fork_epoch)
is_slashing_operation = operation_type in (OperationType.PROPOSER_SLASHING, OperationType.ATTESTER_SLASHING)
# prepare operation # prepare operation
selected_validator_index = None selected_validator_index = None
if operation_type == OperetionType.PROPOSER_SLASHING: if is_slashing_operation:
# avoid slashing the next proposer # avoid slashing the next proposer
future_state = state.copy() future_state = state.copy()
next_slot(spec, future_state) next_slot(spec, future_state)
proposer_index = spec.get_beacon_proposer_index(future_state) proposer_index = spec.get_beacon_proposer_index(future_state)
selected_validator_index = (proposer_index + 1) % len(state.validators) selected_validator_index = (proposer_index + 1) % len(state.validators)
proposer_slashing = get_valid_proposer_slashing( if operation_type == OperationType.PROPOSER_SLASHING:
spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True) proposer_slashing = get_valid_proposer_slashing(
operation_dict = {'proposer_slashings': [proposer_slashing]} spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True)
elif operation_type == OperetionType.ATTESTER_SLASHING: operation_dict = {'proposer_slashings': [proposer_slashing]}
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) else:
operation_dict = {'attester_slashings': [attester_slashing]} # operation_type == OperationType.ATTESTER_SLASHING:
elif operation_type == OperetionType.DEPOSIT: attester_slashing = get_valid_attester_slashing_by_indices(
spec, state,
[selected_validator_index],
signed_1=True, signed_2=True,
)
operation_dict = {'attester_slashings': [attester_slashing]}
elif operation_type == OperationType.DEPOSIT:
# create a new deposit # create a new deposit
selected_validator_index = len(state.validators) selected_validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, selected_validator_index, amount, signed=True) deposit = prepare_state_and_deposit(spec, state, selected_validator_index, amount, signed=True)
operation_dict = {'deposits': [deposit]} operation_dict = {'deposits': [deposit]}
elif operation_type == OperetionType.VOLUNTARY_EXIT: elif operation_type == OperationType.VOLUNTARY_EXIT:
selected_validator_index = 0 selected_validator_index = 0
signed_exits = prepare_signed_exits(spec, state, [selected_validator_index]) signed_exits = prepare_signed_exits(spec, state, [selected_validator_index])
operation_dict = {'voluntary_exits': signed_exits} operation_dict = {'voluntary_exits': signed_exits}
@ -238,22 +254,23 @@ def run_transition_with_operation(state,
blocks.append(pre_tag(signed_block)) blocks.append(pre_tag(signed_block))
def _check_state(): def _check_state():
if operation_type == OperetionType.PROPOSER_SLASHING: if operation_type == OperationType.PROPOSER_SLASHING:
slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index]
assert slashed_proposer.slashed assert slashed_proposer.slashed
elif operation_type == OperetionType.ATTESTER_SLASHING: elif operation_type == OperationType.ATTESTER_SLASHING:
indices = set(attester_slashing.attestation_1.attesting_indices).intersection( indices = set(attester_slashing.attestation_1.attesting_indices).intersection(
attester_slashing.attestation_2.attesting_indices attester_slashing.attestation_2.attesting_indices
) )
assert selected_validator_index in indices
assert len(indices) > 0 assert len(indices) > 0
for validator_index in indices: for validator_index in indices:
assert state.validators[validator_index].slashed assert state.validators[validator_index].slashed
elif operation_type == OperetionType.DEPOSIT: elif operation_type == OperationType.DEPOSIT:
assert not post_spec.is_active_validator( assert not post_spec.is_active_validator(
state.validators[selected_validator_index], state.validators[selected_validator_index],
post_spec.get_current_epoch(state) post_spec.get_current_epoch(state)
) )
elif operation_type == OperetionType.VOLUNTARY_EXIT: elif operation_type == OperationType.VOLUNTARY_EXIT:
validator = state.validators[selected_validator_index] validator = state.validators[selected_validator_index]
assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH
@ -271,11 +288,21 @@ def run_transition_with_operation(state,
_check_state() _check_state()
# after the fork # after the fork
if operation_type == OperetionType.DEPOSIT: if operation_type == OperationType.DEPOSIT:
_transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index) _transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index)
else: else:
# avoid using the slashed validators as block proposers
ignoring_proposers = [selected_validator_index] if is_slashing_operation else None
# continue regular state transition with new spec into next epoch # continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) transition_to_next_epoch_and_append_blocks(
post_spec,
state,
post_tag,
blocks,
only_last_block=True,
ignoring_proposers=ignoring_proposers,
)
yield "blocks", blocks yield "blocks", blocks
yield "post", state yield "post", state

View File

@ -7,7 +7,7 @@ from eth2spec.test.helpers.state import next_epoch
def set_some_new_deposits(spec, state, rng): def set_some_new_deposits(spec, state, rng):
queuing_indices = [] deposited_indices = []
num_validators = len(state.validators) num_validators = len(state.validators)
# Set ~1/10 to just recently deposited # Set ~1/10 to just recently deposited
for index in range(num_validators): for index in range(num_validators):
@ -16,24 +16,21 @@ def set_some_new_deposits(spec, state, rng):
continue continue
if rng.randrange(num_validators) < num_validators // 10: if rng.randrange(num_validators) < num_validators // 10:
mock_deposit(spec, state, index) mock_deposit(spec, state, index)
# Set ~half of selected to eligible for activation
if rng.choice([True, False]): if rng.choice([True, False]):
# Set ~half of selected to eligible for activation
state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state)
else: else:
queuing_indices.append(index) # The validators that just made a deposit
return queuing_indices deposited_indices.append(index)
return deposited_indices
def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, withdrawable_epoch=None, forward=True): def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, forward=True):
""" """
Set some validators' exit_epoch and withdrawable_epoch. Set some validators' exit_epoch and withdrawable_epoch.
If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch. If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch.
""" """
if fraction is None:
# Exit ~1/2
fraction = 0.5
if forward: if forward:
if spec.get_current_epoch(state) < 5: if spec.get_current_epoch(state) < 5:
# Move epochs forward to allow for some validators already exited/withdrawable # Move epochs forward to allow for some validators already exited/withdrawable
@ -67,11 +64,7 @@ def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, wit
return exited_indices return exited_indices
def slash_random_validators(spec, state, rng, fraction=None): def slash_random_validators(spec, state, rng, fraction=0.5):
if fraction is None:
# Slash ~1/2 of validators
fraction = 0.5
slashed_indices = [] slashed_indices = []
for index in range(len(state.validators)): for index in range(len(state.validators)):
# slash at least one validator # slash at least one validator