Merge pull request #2664 from ethereum/new-transition-test-cases

Add new Altair transition tests
This commit is contained in:
Alex Stokes 2021-10-15 09:38:24 -06:00 committed by GitHub
commit bf01e11cae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 956 additions and 126 deletions

View File

@ -1,7 +1,10 @@
from random import Random
from eth2spec.test.context import spec_state_test, with_altair_and_later
from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores, zero_inactivity_scores
from eth2spec.test.helpers.inactivity_scores import (
randomize_inactivity_scores,
zero_inactivity_scores,
)
from eth2spec.test.helpers.state import (
next_epoch,
next_epoch_via_block,

View File

@ -0,0 +1,206 @@
import random
from eth2spec.test.context import (
MINIMAL,
fork_transition_test,
with_presets,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
transition_until_fork,
transition_to_next_epoch_and_append_blocks,
)
from eth2spec.test.helpers.random import (
exit_random_validators,
set_some_activations,
set_some_new_deposits,
)
#
# Exit
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@with_presets([MINIMAL],
reason="only test with enough validators such that at least one exited index is not in sync committee")
def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag):
"""
1/4 validators initiated voluntary exit before the fork,
and are exiting but still active *after* the fork transition.
"""
exited_indices = exit_random_validators(
spec,
state,
rng=random.Random(5566),
fraction=0.25,
exit_epoch=10,
from_epoch=spec.get_current_epoch(state),
)
transition_until_fork(spec, state, fork_epoch)
# check pre state
assert len(exited_indices) > 0
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert fork_epoch < validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
assert not spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# ensure that some of the current sync committee members are exiting
exited_pubkeys = [state.validators[index].pubkey for index in exited_indices]
assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys)))
assert any(set(exited_pubkeys).difference(list(state.current_sync_committee.pubkeys)))
# 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)
# check state
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
assert not post_spec.is_in_inactivity_leak(state)
yield "blocks", blocks
yield "post", state
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag):
"""
1/4 validators initiated voluntary exit before the fork,
and being exited and inactive *right after* the fork transition.
"""
exited_indices = exit_random_validators(
spec,
state,
rng=random.Random(5566),
fraction=0.25,
exit_epoch=fork_epoch,
from_epoch=spec.get_current_epoch(state),
)
transition_until_fork(spec, state, fork_epoch)
# check pre state
assert len(exited_indices) > 0
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert fork_epoch == validator.exit_epoch < spec.FAR_FUTURE_EPOCH
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
assert not spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# check post transition state
for index in exited_indices:
validator = state.validators[index]
assert not validator.slashed
assert not post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
assert not post_spec.is_in_inactivity_leak(state)
# ensure that none of the current sync committee members are exited validators
exited_pubkeys = [state.validators[index].pubkey for index in exited_indices]
assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys)))
# 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)
yield "blocks", blocks
yield "post", state
#
# Activation
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create some deposits before the transition
"""
transition_until_fork(spec, state, fork_epoch)
deposited_indices = set_some_new_deposits(spec, state, rng=random.Random(5566))
assert spec.get_current_epoch(state) < fork_epoch
assert len(deposited_indices) > 0
for validator_index in deposited_indices:
assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state))
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# 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)
yield "blocks", blocks
yield "post", state
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_activation_at_fork_epoch(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create some deposits before the transition
"""
transition_until_fork(spec, state, fork_epoch)
selected_indices = set_some_activations(spec, state, rng=random.Random(5566), activation_epoch=fork_epoch)
assert spec.get_current_epoch(state) < fork_epoch
assert len(selected_indices) > 0
for validator_index in selected_indices:
validator = state.validators[validator_index]
assert not spec.is_active_validator(validator, spec.get_current_epoch(state))
assert validator.activation_epoch == fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# 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)
# now they are active
for validator_index in selected_indices:
validator = state.validators[validator_index]
assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
yield "blocks", blocks
yield "post", state

View File

@ -0,0 +1,63 @@
from eth2spec.test.context import fork_transition_test
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
transition_until_fork,
transition_to_next_epoch_and_append_blocks,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=7)
def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2).
The leaking starts before the fork transition in this case.
"""
transition_until_fork(spec, state, fork_epoch)
assert spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# check post transition state
assert spec.is_in_inactivity_leak(state)
# 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)
yield "blocks", blocks
yield "post", state
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=6)
def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2).
The leaking starts at the fork transition in this case.
"""
transition_until_fork(spec, state, fork_epoch)
assert not spec.is_in_inactivity_leak(state)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
blocks = []
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# check post transition state
assert spec.is_in_inactivity_leak(state)
# 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)
yield "blocks", blocks
yield "post", state

View File

@ -0,0 +1,176 @@
from eth2spec.test.context import (
always_bls,
fork_transition_test,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
OperationType,
run_transition_with_operation,
)
#
# PROPOSER_SLASHING
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.PROPOSER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_proposer_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *before* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.PROPOSER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)
#
# ATTESTER_SLASHING
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_attester_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.ATTESTER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@always_bls
def test_transition_with_attester_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create an attester slashing right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.ATTESTER_SLASHING,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)
#
# DEPOSIT
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_deposit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a deposit right *after* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.DEPOSIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_deposit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a deposit right *before* the transition
"""
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.DEPOSIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)
#
# VOLUNTARY_EXIT
#
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260)
def test_transition_with_voluntary_exit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a voluntary exit right *after* the transition.
fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs.
"""
# Fast forward to the future epoch so that validator can do voluntary exit
state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.VOLUNTARY_EXIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260)
def test_transition_with_voluntary_exit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Create a voluntary exit right *before* the transition.
fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs.
"""
# Fast forward to the future epoch so that validator can do voluntary exit
state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
yield from run_transition_with_operation(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type=OperationType.VOLUNTARY_EXIT,
operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1,
)

View File

@ -0,0 +1,73 @@
import random
from eth2spec.test.context import (
MINIMAL,
fork_transition_test,
with_presets,
)
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
transition_to_next_epoch_and_append_blocks,
transition_until_fork,
)
from eth2spec.test.helpers.random import (
slash_random_validators,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1)
@with_presets([MINIMAL],
reason="only test with enough validators such that at least one exited index is not in sync committee")
def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag):
"""
1/4 validators are slashed but still active at the fork transition.
"""
# slash 1/4 validators
slashed_indices = slash_random_validators(spec, state, rng=random.Random(5566), fraction=0.25)
assert len(slashed_indices) > 0
# check if some validators are slashed but still active
for validator_index in slashed_indices:
validator = state.validators[validator_index]
assert validator.slashed
assert spec.is_active_validator(validator, spec.get_current_epoch(state))
assert not spec.is_in_inactivity_leak(state)
transition_until_fork(spec, state, fork_epoch)
assert spec.get_current_epoch(state) < fork_epoch
yield "pre", state
# irregular state transition to handle fork:
state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
# ensure that some of the current sync committee members are slashed
slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices]
assert any(set(slashed_pubkeys).intersection(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
# since the proposer might have been slashed, here we only create blocks with non-slashed proposers
blocks = []
transition_to_next_epoch_and_append_blocks(
post_spec,
state,
post_tag,
blocks,
only_last_block=True,
ignoring_proposers=slashed_indices,
)
# check post state
for validator in state.validators:
assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state))
assert not post_spec.is_in_inactivity_leak(state)
yield "blocks", blocks
yield "post", state

View File

@ -1,83 +1,18 @@
import random
from eth2spec.test.context import fork_transition_test
from eth2spec.test.helpers.constants import PHASE0, ALTAIR
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_signed_block
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block
from eth2spec.test.helpers.state import (
next_epoch_via_signed_block,
)
from eth2spec.test.helpers.attestations import next_slots_with_attestations
def _state_transition_and_sign_block_at_slot(spec, state):
"""
Cribbed from ``transition_unsigned_block`` helper
where the early parts of the state transition have already
been applied to ``state``.
Used to produce a block during an irregular state transition.
"""
block = build_empty_block(spec, state)
assert state.latest_block_header.slot < block.slot
assert state.slot == block.slot
spec.process_block(state, block)
block.state_root = state.hash_tree_root()
return sign_block(spec, state, block)
def _all_blocks(_):
return True
def _skip_slots(*slots):
"""
Skip making a block if its slot is
passed as an argument to this filter
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 not in slots
return f
def _no_blocks(_):
return False
def _only_at(slot):
"""
Only produce a block if its slot is ``slot``.
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 == slot
return f
def _state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks):
assert state.slot < to_slot
while state.slot < to_slot:
should_make_block = block_filter(state)
if should_make_block:
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield signed_block
else:
next_slot(spec, state)
def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True):
spec.process_slots(state, state.slot + 1)
assert state.slot % spec.SLOTS_PER_EPOCH == 0
assert spec.get_current_epoch(state) == fork_epoch
state = post_spec.upgrade_to_altair(state)
assert state.fork.epoch == fork_epoch
assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION
assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION
if with_block:
return state, _state_transition_and_sign_block_at_slot(post_spec, state)
else:
return state, None
from eth2spec.test.helpers.fork_transition import (
do_altair_fork,
no_blocks,
only_at,
skip_slots,
state_transition_across_slots,
transition_to_next_epoch_and_append_blocks,
)
@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
@ -95,19 +30,15 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot)
state_transition_across_slots(spec, state, to_slot)
])
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot)
])
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 1
@ -136,18 +67,14 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec,
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot)
state_transition_across_slots(spec, state, to_slot)
])
# irregular state transition to handle fork:
state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot)
])
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 1
@ -178,19 +105,15 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot, block_filter=_skip_slots(last_slot_of_pre_fork))
state_transition_across_slots(spec, state, to_slot, block_filter=skip_slots(last_slot_of_pre_fork))
])
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot)
])
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 1
@ -221,18 +144,18 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot, block_filter=_no_blocks)
state_transition_across_slots(spec, state, to_slot, block_filter=no_blocks)
])
# irregular state transition to handle fork:
state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False)
# continue regular state transition with new spec into next epoch
to_slot = post_spec.SLOTS_PER_EPOCH + state.slot
last_slot = (fork_epoch + 1) * post_spec.SLOTS_PER_EPOCH
blocks.extend([
post_tag(block) for block in
_state_transition_across_slots(post_spec, state, to_slot, block_filter=_only_at(last_slot))
state_transition_across_slots(post_spec, state, to_slot, block_filter=only_at(last_slot))
])
assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
@ -292,7 +215,7 @@ def _run_transition_test_with_attestations(state,
assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition with new spec into next epoch
@ -405,11 +328,11 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe
blocks = []
blocks.extend([
pre_tag(block) for block in
_state_transition_across_slots(spec, state, to_slot)
state_transition_across_slots(spec, state, to_slot)
])
# irregular state transition to handle fork:
state, block = _do_altair_fork(state, spec, post_spec, fork_epoch)
state, block = do_altair_fork(state, spec, post_spec, fork_epoch)
blocks.append(post_tag(block))
# continue regular state transition but add attestations

View File

@ -0,0 +1,335 @@
from enum import Enum, auto
from eth2spec.test.helpers.attester_slashings import (
get_valid_attester_slashing_by_indices,
)
from eth2spec.test.helpers.attestations import next_slots_with_attestations
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
build_empty_block,
sign_block,
)
from eth2spec.test.helpers.deposits import (
prepare_state_and_deposit,
)
from eth2spec.test.helpers.proposer_slashings import (
get_valid_proposer_slashing,
)
from eth2spec.test.helpers.state import (
next_slot,
state_transition_and_sign_block,
transition_to,
)
from eth2spec.test.helpers.voluntary_exits import (
prepare_signed_exits,
)
class OperationType(Enum):
PROPOSER_SLASHING = auto()
ATTESTER_SLASHING = auto()
DEPOSIT = auto()
VOLUNTARY_EXIT = auto()
def _set_operations_by_dict(block, operation_dict):
for key, value in operation_dict.items():
setattr(block.body, key, value)
def _state_transition_and_sign_block_at_slot(spec,
state,
operation_dict=None):
"""
Cribbed from ``transition_unsigned_block`` helper
where the early parts of the state transition have already
been applied to ``state``.
Used to produce a block during an irregular state transition.
The optional `operation_dict` is a dict of {'<BeaconBlockBody field>': <value>}.
This is used for assigning the block operations.
p.s. we can't just pass `body` and assign it because randao_reveal and eth1_data was set in `build_empty_block`
Thus use dict to pass operations.
"""
block = build_empty_block(spec, state)
if operation_dict:
_set_operations_by_dict(block, operation_dict)
assert state.latest_block_header.slot < block.slot
assert state.slot == block.slot
spec.process_block(state, block)
block.state_root = state.hash_tree_root()
return sign_block(spec, state, block)
def _all_blocks(_):
return True
def skip_slots(*slots):
"""
Skip making a block if its slot is
passed as an argument to this filter
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 not in slots
return f
def no_blocks(_):
return False
def only_at(slot):
"""
Only produce a block if its slot is ``slot``.
"""
def f(state_at_prior_slot):
return state_at_prior_slot.slot + 1 == slot
return f
def state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks):
assert state.slot < to_slot
while state.slot < to_slot:
should_make_block = block_filter(state)
if should_make_block:
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield signed_block
else:
next_slot(spec, state)
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`
and ensure that the result state was computed with a block with slot >= to_slot.
"""
assert state.slot < to_slot
found_valid = False
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()
next_slot(spec, future_state)
proposer_index = spec.get_beacon_proposer_index(future_state)
if proposer_index not in ignoring_proposers:
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield signed_block
if state.slot >= to_slot:
found_valid = True
else:
next_slot(spec, state)
def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=None):
spec.process_slots(state, state.slot + 1)
assert state.slot % spec.SLOTS_PER_EPOCH == 0
assert spec.get_current_epoch(state) == fork_epoch
state = post_spec.upgrade_to_altair(state)
assert state.fork.epoch == fork_epoch
assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION
assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION
if with_block:
return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict)
else:
return state, None
def transition_until_fork(spec, state, fork_epoch):
to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1
transition_to(spec, state, to_slot)
def _transition_until_fork_minus_one(spec, state, fork_epoch):
to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 2
transition_to(spec, state, to_slot)
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
if only_last_block:
block_filter = only_at(to_slot)
else:
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([
post_tag(block) for block in
result_blocks
])
def run_transition_with_operation(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
operation_type,
operation_at_slot):
"""
Generate `operation_type` operation with the spec before fork.
The operation would be included into the block at `operation_at_slot`.
"""
is_at_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH
is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1
assert is_at_fork or is_right_before_fork
if is_at_fork:
transition_until_fork(spec, state, fork_epoch)
elif is_right_before_fork:
_transition_until_fork_minus_one(spec, state, fork_epoch)
is_slashing_operation = operation_type in (OperationType.PROPOSER_SLASHING, OperationType.ATTESTER_SLASHING)
# prepare operation
selected_validator_index = None
if is_slashing_operation:
# avoid slashing the next proposer
future_state = state.copy()
next_slot(spec, future_state)
proposer_index = spec.get_beacon_proposer_index(future_state)
selected_validator_index = (proposer_index + 1) % len(state.validators)
if operation_type == OperationType.PROPOSER_SLASHING:
proposer_slashing = get_valid_proposer_slashing(
spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True)
operation_dict = {'proposer_slashings': [proposer_slashing]}
else:
# operation_type == OperationType.ATTESTER_SLASHING:
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
selected_validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
deposit = prepare_state_and_deposit(spec, state, selected_validator_index, amount, signed=True)
operation_dict = {'deposits': [deposit]}
elif operation_type == OperationType.VOLUNTARY_EXIT:
selected_validator_index = 0
signed_exits = prepare_signed_exits(spec, state, [selected_validator_index])
operation_dict = {'voluntary_exits': signed_exits}
blocks = []
if is_right_before_fork:
# add a block with operation.
block = build_empty_block_for_next_slot(spec, state)
_set_operations_by_dict(block, operation_dict)
signed_block = state_transition_and_sign_block(spec, state, block)
blocks.append(pre_tag(signed_block))
def _check_state():
if operation_type == OperationType.PROPOSER_SLASHING:
slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index]
assert slashed_proposer.slashed
elif operation_type == OperationType.ATTESTER_SLASHING:
indices = set(attester_slashing.attestation_1.attesting_indices).intersection(
attester_slashing.attestation_2.attesting_indices
)
assert selected_validator_index in indices
assert len(indices) > 0
for validator_index in indices:
assert state.validators[validator_index].slashed
elif operation_type == OperationType.DEPOSIT:
assert not post_spec.is_active_validator(
state.validators[selected_validator_index],
post_spec.get_current_epoch(state)
)
elif operation_type == OperationType.VOLUNTARY_EXIT:
validator = state.validators[selected_validator_index]
assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH
if is_right_before_fork:
_check_state()
yield "pre", state
# irregular state transition to handle fork:
_operation_at_slot = operation_dict if is_at_fork else None
state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot)
blocks.append(post_tag(block))
if is_at_fork:
_check_state()
# after the fork
if operation_type == OperationType.DEPOSIT:
_transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index)
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
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 "post", state
def _transition_until_active(post_spec, state, post_tag, blocks, validator_index):
# continue regular state transition with new spec into next epoch
transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks)
# finalize activation_eligibility_epoch
_, blocks_in_epoch, state = next_slots_with_attestations(
post_spec,
state,
post_spec.SLOTS_PER_EPOCH * 2,
fill_cur_epoch=True,
fill_prev_epoch=True,
)
blocks.extend([post_tag(block) for block in blocks_in_epoch])
assert state.finalized_checkpoint.epoch >= state.validators[validator_index].activation_eligibility_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)
assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH
to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH
blocks.extend([
post_tag(block) for block in
state_transition_across_slots(post_spec, state, to_slot, block_filter=only_at(to_slot))
])
assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state))

View File

@ -6,7 +6,28 @@ from eth2spec.test.helpers.deposits import mock_deposit
from eth2spec.test.helpers.state import next_epoch
def set_some_activations(spec, state, rng, activation_epoch=None):
if activation_epoch is None:
activation_epoch = spec.get_current_epoch(state)
num_validators = len(state.validators)
selected_indices = []
for index in range(num_validators):
# If is slashed or exiting, skip
if state.validators[index].slashed or state.validators[index].exit_epoch != spec.FAR_FUTURE_EPOCH:
continue
# Set ~1/10 validators' activation_eligibility_epoch and activation_epoch
if rng.randrange(num_validators) < num_validators // 10:
state.validators[index].activation_eligibility_epoch = max(
int(activation_epoch) - int(spec.MAX_SEED_LOOKAHEAD) - 1,
spec.GENESIS_EPOCH,
)
state.validators[index].activation_epoch = activation_epoch
selected_indices.append(index)
return selected_indices
def set_some_new_deposits(spec, state, rng):
deposited_indices = []
num_validators = len(state.validators)
# Set ~1/10 to just recently deposited
for index in range(num_validators):
@ -15,46 +36,64 @@ def set_some_new_deposits(spec, state, rng):
continue
if rng.randrange(num_validators) < num_validators // 10:
mock_deposit(spec, state, index)
# Set ~half of selected to eligible for activation
if rng.choice([True, False]):
# Set ~half of selected to eligible for activation
state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state)
else:
# The validators that just made a deposit
deposited_indices.append(index)
return deposited_indices
def exit_random_validators(spec, state, rng, fraction=None):
if fraction is None:
# Exit ~1/2
fraction = 0.5
def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, from_epoch=None):
"""
Set some validators' exit_epoch and withdrawable_epoch.
if spec.get_current_epoch(state) < 5:
# Move epochs forward to allow for some validators already exited/withdrawable
for _ in range(5):
next_epoch(spec, state)
If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch.
"""
if from_epoch is None:
from_epoch = spec.MAX_SEED_LOOKAHEAD + 1
epoch_diff = int(from_epoch) - int(spec.get_current_epoch(state))
for _ in range(epoch_diff):
# NOTE: if `epoch_diff` is negative, then this loop body does not execute.
next_epoch(spec, state)
current_epoch = spec.get_current_epoch(state)
exited_indices = []
for index in spec.get_active_validator_indices(state, current_epoch):
sampled = rng.random() < fraction
if not sampled:
continue
exited_indices.append(index)
validator = state.validators[index]
validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3])
# ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch)
if rng.choice([True, False]):
validator.withdrawable_epoch = current_epoch
if exit_epoch is None:
assert withdrawable_epoch is None
validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3])
# ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch)
if rng.choice([True, False]):
validator.withdrawable_epoch = current_epoch
else:
validator.withdrawable_epoch = current_epoch + 1
else:
validator.withdrawable_epoch = current_epoch + 1
validator.exit_epoch = exit_epoch
if withdrawable_epoch is None:
validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY
else:
validator.withdrawable_epoch = withdrawable_epoch
return exited_indices
def slash_random_validators(spec, state, rng, fraction=None):
if fraction is None:
# Slash ~1/2 of validators
fraction = 0.5
def slash_random_validators(spec, state, rng, fraction=0.5):
slashed_indices = []
for index in range(len(state.validators)):
# slash at least one validator
sampled = rng.random() < fraction
if index == 0 or sampled:
spec.slash_validator(state, index)
slashed_indices.append(index)
return slashed_indices
def randomize_epoch_participation(spec, state, epoch, rng):
@ -123,7 +162,7 @@ def randomize_attestation_participation(spec, state, rng=Random(8020)):
randomize_epoch_participation(spec, state, spec.get_current_epoch(state), rng)
def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fraction=None):
def randomize_state(spec, state, rng=Random(8020), exit_fraction=0.5, slash_fraction=0.5):
set_some_new_deposits(spec, state, rng)
exit_random_validators(spec, state, rng, fraction=exit_fraction)
slash_random_validators(spec, state, rng, fraction=slash_fraction)

View File

@ -1,7 +1,13 @@
from typing import Iterable
from eth2spec.test.helpers.constants import ALTAIR, MINIMAL, MAINNET, PHASE0
from eth2spec.test.altair.transition import test_transition as test_altair_transition
from eth2spec.test.altair.transition import (
test_transition as test_altair_transition,
test_activations_and_exits as test_altair_activations_and_exits,
test_leaking as test_altair_leaking,
test_slashing as test_altair_slashing,
test_operations as test_altair_operations,
)
from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing
from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests
@ -25,7 +31,13 @@ def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_n
return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn)
TRANSITION_TESTS = ((PHASE0, ALTAIR, test_altair_transition),)
TRANSITION_TESTS = (
(PHASE0, ALTAIR, test_altair_transition),
(PHASE0, ALTAIR, test_altair_activations_and_exits),
(PHASE0, ALTAIR, test_altair_leaking),
(PHASE0, ALTAIR, test_altair_slashing),
(PHASE0, ALTAIR, test_altair_operations),
)
if __name__ == "__main__":