Skip to content

Commit

Permalink
Merge pull request #2426 from ralexstokes/more-altair-fork-transition…
Browse files Browse the repository at this point in the history
…-tests

More Altair fork transition tests
  • Loading branch information
djrtwo authored May 21, 2021
2 parents 378d167 + ba6d193 commit a52d67d
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 28 deletions.
204 changes: 198 additions & 6 deletions tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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
from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_block
from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block
from eth2spec.test.helpers.attestations import next_slots_with_attestations


def _state_transition_and_sign_block_at_slot(spec, state):
Expand Down Expand Up @@ -64,7 +66,7 @@ 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.compute_epoch_at_slot(state.slot) == fork_epoch
assert spec.get_current_epoch(state) == fork_epoch

state = post_spec.upgrade_to_altair(state)

Expand Down Expand Up @@ -108,7 +110,7 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag
])

assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1
assert post_spec.get_current_epoch(state) == fork_epoch + 1

slots_with_blocks = [block.message.slot for block in blocks]
assert len(set(slots_with_blocks)) == len(slots_with_blocks)
Expand Down Expand Up @@ -148,7 +150,7 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec,
])

assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1
assert post_spec.get_current_epoch(state) == fork_epoch + 1

slots_with_blocks = [block.message.slot for block in blocks]
assert len(set(slots_with_blocks)) == len(slots_with_blocks)
Expand Down Expand Up @@ -191,7 +193,7 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp
])

assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1
assert post_spec.get_current_epoch(state) == fork_epoch + 1

slots_with_blocks = [block.message.slot for block in blocks]
assert len(set(slots_with_blocks)) == len(slots_with_blocks)
Expand Down Expand Up @@ -234,11 +236,201 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr
])

assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.compute_epoch_at_slot(state.slot) == fork_epoch + 1
assert post_spec.get_current_epoch(state) == fork_epoch + 1

slots_with_blocks = [block.message.slot for block in blocks]
assert len(slots_with_blocks) == 1
assert slots_with_blocks[0] == last_slot

yield "blocks", blocks
yield "post", state


def _run_transition_test_with_attestations(state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
participation_fn=None,
expect_finality=True):
yield "pre", state

current_epoch = spec.get_current_epoch(state)
assert current_epoch < fork_epoch
assert current_epoch == spec.GENESIS_EPOCH

# skip genesis epoch to avoid dealing with some edge cases...
block = next_epoch_via_block(spec, state)

# regular state transition until fork:
fill_cur_epoch = False
fill_prev_epoch = True
blocks = [pre_tag(sign_block(spec, state, block))]
current_epoch = spec.get_current_epoch(state)
for _ in range(current_epoch, fork_epoch - 1):
_, blocks_in_epoch, state = next_slots_with_attestations(
spec,
state,
spec.SLOTS_PER_EPOCH,
fill_cur_epoch,
fill_prev_epoch,
participation_fn=participation_fn,
)
blocks.extend([pre_tag(block) for block in blocks_in_epoch])

_, blocks_in_epoch, state = next_slots_with_attestations(
spec,
state,
spec.SLOTS_PER_EPOCH - 1,
fill_cur_epoch,
fill_prev_epoch,
participation_fn=participation_fn,
)
blocks.extend([pre_tag(block) for block in blocks_in_epoch])
assert spec.get_current_epoch(state) == fork_epoch - 1
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)
blocks.append(post_tag(block))

# continue regular state transition with new spec into next epoch
for _ in range(4):
_, blocks_in_epoch, state = next_slots_with_attestations(
post_spec,
state,
post_spec.SLOTS_PER_EPOCH,
fill_cur_epoch,
fill_prev_epoch,
participation_fn=participation_fn,
)
blocks.extend([post_tag(block) for block in blocks_in_epoch])

assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 4

if expect_finality:
assert state.current_justified_checkpoint.epoch == fork_epoch + 2
assert state.finalized_checkpoint.epoch == fork_epoch
else:
assert state.current_justified_checkpoint.epoch == spec.GENESIS_EPOCH
assert state.finalized_checkpoint.epoch == spec.GENESIS_EPOCH

assert len(blocks) == (fork_epoch + 3) * post_spec.SLOTS_PER_EPOCH + 1
assert len(blocks) == len(set(blocks))

blocks_without_attestations = [block for block in blocks if len(block.message.body.attestations) == 0]
assert len(blocks_without_attestations) == 2
slots_without_attestations = [b.message.slot for b in blocks_without_attestations]

assert set(slots_without_attestations) == set([spec.SLOTS_PER_EPOCH, fork_epoch * spec.SLOTS_PER_EPOCH])

yield "blocks", blocks
yield "post", state


@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3)
def test_transition_with_finality(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Transition from the initial ``state`` to the epoch after the ``fork_epoch``,
including attestations so as to produce finality through the fork boundary.
"""
yield from _run_transition_test_with_attestations(state, fork_epoch, spec, post_spec, pre_tag, post_tag)


@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3)
def test_transition_with_random_three_quarters_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Transition from the initial ``state`` to the epoch after the ``fork_epoch``,
including attestations so as to produce finality through the fork boundary.
"""
rng = random.Random(1337)

def _drop_random_quarter(_slot, _index, indices):
# still finalize, but drop some attestations
committee_len = len(indices)
assert committee_len >= 4
filter_len = committee_len // 4
participant_count = committee_len - filter_len
return rng.sample(indices, participant_count)

yield from _run_transition_test_with_attestations(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
participation_fn=_drop_random_quarter
)


@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3)
def test_transition_with_random_half_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
rng = random.Random(2020)

def _drop_random_half(_slot, _index, indices):
# drop enough attestations to not finalize
committee_len = len(indices)
assert committee_len >= 2
filter_len = committee_len // 2
participant_count = committee_len - filter_len
return rng.sample(indices, participant_count)

yield from _run_transition_test_with_attestations(
state,
fork_epoch,
spec,
post_spec,
pre_tag,
post_tag,
participation_fn=_drop_random_half,
expect_finality=False
)


@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2)
def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag):
"""
Transition from the initial ``state`` to the ``fork_epoch`` with no attestations,
then transition forward with enough attestations to finalize the fork epoch.
"""
yield "pre", state

assert spec.get_current_epoch(state) < fork_epoch

# regular state transition until fork:
to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1
blocks = []
blocks.extend([
pre_tag(block) for block in
_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)
blocks.append(post_tag(block))

# continue regular state transition but add attestations
# for enough epochs to finalize the ``fork_epoch``
block = next_epoch_via_block(post_spec, state)
blocks.append(post_tag(sign_block(post_spec, state, block)))
for _ in range(4):
_, blocks_in_epoch, state = next_slots_with_attestations(
post_spec,
state,
post_spec.SLOTS_PER_EPOCH,
False,
True,
)
blocks.extend([post_tag(block) for block in blocks_in_epoch])

assert state.slot % post_spec.SLOTS_PER_EPOCH == 0
assert post_spec.get_current_epoch(state) == fork_epoch + 5

assert state.current_justified_checkpoint.epoch == fork_epoch + 3
assert state.finalized_checkpoint.epoch == fork_epoch + 1

yield "blocks", blocks
yield "post", state
80 changes: 60 additions & 20 deletions tests/core/pyspec/eth2spec/test/helpers/attestations.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,43 +219,83 @@ def add_attestations_to_state(spec, state, attestations, slot):
spec.process_attestation(state, attestation)


def next_epoch_with_attestations(spec,
def _get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None, on_time=True):
committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
for index in range(committees_per_slot):
def participants_filter(comm):
if participation_fn is None:
return comm
else:
return participation_fn(state.slot, index, comm)
# if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block
yield get_valid_attestation(
spec,
state,
slot_to_attest,
index=index,
signed=True,
on_time=on_time,
filter_participant_set=participants_filter
)


def next_slots_with_attestations(spec,
state,
slot_count,
fill_cur_epoch,
fill_prev_epoch):
assert state.slot % spec.SLOTS_PER_EPOCH == 0

fill_prev_epoch,
participation_fn=None):
"""
participation_fn: (slot, committee_index, committee_indices_set) -> participants_indices_set
"""
post_state = state.copy()
signed_blocks = []
for _ in range(spec.SLOTS_PER_EPOCH):
for _ in range(slot_count):
block = build_empty_block_for_next_slot(spec, post_state)
if fill_cur_epoch and post_state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY:
slot_to_attest = post_state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1
committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)):
for index in range(committees_per_slot):
# if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block

cur_attestation = get_valid_attestation(
spec, post_state, slot_to_attest,
index=index, signed=True, on_time=True
)
block.body.attestations.append(cur_attestation)

attestations = _get_valid_attestation_at_slot(
post_state,
spec,
slot_to_attest,
participation_fn=participation_fn
)
for attestation in attestations:
block.body.attestations.append(attestation)
if fill_prev_epoch:
slot_to_attest = post_state.slot - spec.SLOTS_PER_EPOCH + 1
committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest))
for index in range(committees_per_slot):
prev_attestation = get_valid_attestation(
spec, post_state, slot_to_attest, index=index, signed=True, on_time=False)
block.body.attestations.append(prev_attestation)
attestations = _get_valid_attestation_at_slot(
post_state,
spec,
slot_to_attest,
on_time=False,
participation_fn=participation_fn
)
for attestation in attestations:
block.body.attestations.append(attestation)

signed_block = state_transition_and_sign_block(spec, post_state, block)
signed_blocks.append(signed_block)

return state, signed_blocks, post_state


def next_epoch_with_attestations(spec,
state,
fill_cur_epoch,
fill_prev_epoch):
assert state.slot % spec.SLOTS_PER_EPOCH == 0

return next_slots_with_attestations(
spec,
state,
spec.SLOTS_PER_EPOCH,
fill_cur_epoch,
fill_prev_epoch,
)


def prepare_state_with_attestations(spec, state, participation_fn=None):
"""
Prepare state with attestations according to the ``participation_fn``.
Expand Down
3 changes: 2 additions & 1 deletion tests/core/pyspec/eth2spec/test/helpers/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ def transition_unsigned_block(spec, state, block):
assert state.latest_block_header.slot < block.slot # There may not already be a block in this slot or past it.
assert state.slot == block.slot # The block must be for this slot
spec.process_block(state, block)
return block


def apply_empty_block(spec, state, slot=None):
"""
Transition via an empty block (on current slot, assuming no block has been applied yet).
"""
block = build_empty_block(spec, state, slot)
transition_unsigned_block(spec, state, block)
return transition_unsigned_block(spec, state, block)


def build_empty_block(spec, state, slot=None):
Expand Down
2 changes: 1 addition & 1 deletion tests/core/pyspec/eth2spec/test/helpers/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def next_epoch_via_block(spec, state):
"""
Transition to the start slot of the next epoch via a full block transition
"""
apply_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH)
return apply_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH)


def get_state_root(spec, state, slot) -> bytes:
Expand Down

0 comments on commit a52d67d

Please sign in to comment.