From 9ed907727e77a46b36ac8ae3a4781041cce455f7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 5 Oct 2021 20:00:45 +1100 Subject: [PATCH 01/22] Specify format for --terminal-total-difficulty --- specs/merge/client-settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/client-settings.md b/specs/merge/client-settings.md index 287e557db..4a4b38d3e 100644 --- a/specs/merge/client-settings.md +++ b/specs/merge/client-settings.md @@ -16,7 +16,7 @@ This document specifies configurable settings that clients must implement for th ### Override terminal total difficulty -To coordinate manual overrides to [`TERMINAL_TOTAL_DIFFICULTY`](./beacon-chain.md#Transition-settings) parameter, clients must provide `--terminal-total-difficulty-override` as a configurable setting. The value provided by this setting must take precedence over pre-configured `TERMINAL_TOTAL_DIFFICULTY` parameter. +To coordinate manual overrides to [`TERMINAL_TOTAL_DIFFICULTY`](./beacon-chain.md#Transition-settings) parameter, clients must provide `--terminal-total-difficulty-override` as a configurable setting. The value provided by this setting must take precedence over pre-configured `TERMINAL_TOTAL_DIFFICULTY` parameter. Clients should accept the setting as a decimal value (i.e., *not* hexadecimal). Except under exceptional scenarios, this setting is expected to not be used. Sufficient warning to the user about this exceptional configurable setting should be provided. From 130b81bf55489827c67a46522e8851665f825915 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 10 Oct 2021 10:47:59 -0400 Subject: [PATCH 02/22] remove notify_consensus_validated in accordance with POS_CONSENSUS_VALIDATED removal from eip 3675 --- setup.py | 3 --- specs/merge/beacon-chain.md | 23 +++-------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/setup.py b/setup.py index b20036fd2..3dcaaa44f 100644 --- a/setup.py +++ b/setup.py @@ -527,9 +527,6 @@ class NoopExecutionEngine(ExecutionEngine): def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: return True - def notify_consensus_validated(self: ExecutionEngine, block_hash: Hash32, valid: bool) -> None: - pass - def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None: pass diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index b3d184855..1b56ca37c 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -31,7 +31,6 @@ - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Execution engine](#execution-engine) - [`execute_payload`](#execute_payload) - - [`notify_consensus_validated`](#notify_consensus_validated) - [Block processing](#block-processing) - [Execution payload processing](#execution-payload-processing) - [`is_valid_gas_limit`](#is_valid_gas_limit) @@ -234,13 +233,11 @@ The implementation-dependent `ExecutionEngine` protocol encapsulates the executi * a state object `self.execution_state` of type `ExecutionState` * a state transition function `self.execute_payload` which applies changes to the `self.execution_state` -* a function `self.notify_consensus_validated` which signals that the beacon block containing the execution payload -is valid with respect to the consensus rule set -*Note*: `execute_payload` and `notify_consensus_validated` are functions accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol. +*Note*: `execute_payload` is a function accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol. -The body of each of these functions is implementation dependent. -The Engine API may be used to implement them with an external execution engine. +The body of this function is implementation dependent. +The Engine API may be used to implement this and similarly defined functions via an external execution engine. #### `execute_payload` @@ -252,20 +249,6 @@ def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) ... ``` -#### `notify_consensus_validated` - -```python -def notify_consensus_validated(self: ExecutionEngine, block_hash: Hash32, valid: bool) -> None: - ... -``` - -The inputs to this function depend on the result of the state transition. A call to `notify_consensus_validated` must be made after the [`state_transition`](../phase0/beacon-chain.md#beacon-chain-state-transition-function) function finishes. The value of the `valid` parameter must be set as follows: - -* `True` if `state_transition` function call succeeds -* `False` if `state_transition` function call fails - -*Note*: The call of the `notify_consensus_validated` function with `valid = True` maps on the `POS_CONSENSUS_VALIDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions). - ### Block processing *Note*: The call to the `process_execution_payload` must happen before the call to the `process_randao` as the former depends on the `randao_mix` computed with the reveal of the previous block. From e5bbbc5191b6080de8047d3b6f8cb694758e1bca Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 11 Oct 2021 16:25:01 -0600 Subject: [PATCH 03/22] base_fee_per_gas to uint256 --- specs/merge/beacon-chain.md | 6 ++---- tests/core/pyspec/eth2spec/test/helpers/genesis.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1b56ca37c..f5977db5e 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -144,8 +144,6 @@ class BeaconState(Container): #### `ExecutionPayload` -*Note*: The `base_fee_per_gas` field is serialized in little-endian. - ```python class ExecutionPayload(Container): # Execution block header fields @@ -160,7 +158,7 @@ class ExecutionPayload(Container): gas_used: uint64 timestamp: uint64 extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: Bytes32 # base fee introduced in EIP-1559, little-endian serialized + base_fee_per_gas: uint256 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] @@ -182,7 +180,7 @@ class ExecutionPayloadHeader(Container): gas_used: uint64 timestamp: uint64 extra_data: ByteList[MAX_EXTRA_DATA_BYTES] - base_fee_per_gas: Bytes32 + base_fee_per_gas: uint256 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions_root: Root diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index cd96c8168..408295072 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -33,7 +33,7 @@ def get_sample_genesis_execution_payload_header(spec, random=eth1_block_hash, block_number=0, gas_limit=30000000, - base_fee_per_gas=spec.Bytes32('0x00ca9a3b00000000000000000000000000000000000000000000000000000000'), + base_fee_per_gas=1000000000, block_hash=eth1_block_hash, transactions_root=spec.Root(b'\x56' * 32), ) From 4dd8b7c98ae0de5a5de37787c4aee969c442e828 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 12 Oct 2021 20:35:18 +0800 Subject: [PATCH 04/22] [WIP] Add new transition tests --- .../test_process_inactivity_updates.py | 20 +- .../test/altair/transition/test_transition.py | 409 +++++++++++++++++- .../test/helpers/inactivity_scores.py | 23 + .../pyspec/eth2spec/test/helpers/random.py | 5 + 4 files changed, 438 insertions(+), 19 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index 2a971f4f0..f262bcdd2 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -1,7 +1,11 @@ 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, + slash_some_validators_for_inactivity_scores_test, +) from eth2spec.test.helpers.state import ( next_epoch, next_epoch_via_block, @@ -201,20 +205,6 @@ def test_random_inactivity_scores_full_participation_leaking(spec, state): assert spec.is_in_inactivity_leak(state) -def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040)): - # ``run_inactivity_scores_test`` runs at the next epoch from `state`. - # We retrieve the proposer of this future state to avoid - # accidentally slashing that validator - future_state = state.copy() - next_epoch_via_block(spec, future_state) - - proposer_index = spec.get_beacon_proposer_index(future_state) - # Slash ~1/4 of validaors - for validator_index in range(len(state.validators)): - if rng.choice(range(4)) == 0 and validator_index != proposer_index: - spec.slash_validator(state, validator_index) - - @with_altair_and_later @spec_state_test def test_some_slashed_zero_scores_full_participation(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index 62740df4e..9996a1278 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -1,12 +1,24 @@ 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.state import ( + next_epoch_via_signed_block, + next_slot, + state_transition_and_sign_block, + transition_to, +) 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.attestations import next_slots_with_attestations +from eth2spec.test.helpers.random import set_some_new_deposits +from eth2spec.test.helpers.inactivity_scores import ( + slash_some_validators_for_inactivity_scores_test, +) -def _state_transition_and_sign_block_at_slot(spec, state): +def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): """ Cribbed from ``transition_unsigned_block`` helper where the early parts of the state transition have already @@ -15,6 +27,9 @@ def _state_transition_and_sign_block_at_slot(spec, state): Used to produce a block during an irregular state transition. """ block = build_empty_block(spec, state) + # FIXME: not just passing `deposits` + if deposits is not None: + block.body.deposits = deposits assert state.latest_block_header.slot < block.slot assert state.slot == block.slot @@ -62,7 +77,29 @@ def _state_transition_across_slots(spec, state, to_slot, block_filter=_all_block next_slot(spec, state) -def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True): +def _state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, 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. + """ + assert state.slot < to_slot + + found_valid = False + while state.slot < to_slot or not found_valid: + 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, deposits=None): spec.process_slots(state, state.slot + 1) assert state.slot % spec.SLOTS_PER_EPOCH == 0 @@ -75,11 +112,25 @@ def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True): 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) + return state, _state_transition_and_sign_block_at_slot(post_spec, state, deposits=deposits) else: return state, None +def _set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): + """ + Set some valdiators' exit_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 + + @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -434,3 +485,353 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe yield "blocks", blocks yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) +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 + selected_indices = slash_some_validators_for_inactivity_scores_test( + spec, state, rng=random.Random(5566), fraction=0.25) + assert len(selected_indices) > 0 + + # check if some validators are slashed but still active + for validator_index in selected_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) + + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + 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)) + + # 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 + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, selected_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 + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_one_fourth_exiting_validators_exit_post_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 exiting but still active validators at the fork transition. + """ + exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) + + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + # 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))) + + # 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) + ]) + + # 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 exiting but still active validators at the fork transition. + """ + exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) + + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + # 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 + 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) + ]) + + yield "blocks", blocks + yield "post", state + + +@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. + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + 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 + 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) + ]) + + 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. + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + 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 + 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) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) +def test_transition_with_leaking_post_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 after the fork transition in this case. + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + 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 not spec.is_in_inactivity_leak(state) + + # 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) + ]) + + # check state again + assert spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) +def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create some deposits before the transition + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + + assert spec.get_current_epoch(state) < fork_epoch + assert len(queuing_indices) > 0 + for validator_index in queuing_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 + 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) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) +def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a deposit at the transition + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + yield "pre", state + + # create a new deposit + validator_index = len(state.validators) + amount = post_spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) + + # irregular state transition to handle fork: + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) + blocks = [] + blocks.append(post_tag(block)) + + assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + # 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) + ]) + + # finalize activation_eligibility_epoch + _, blocks_in_epoch, state = next_slots_with_attestations( + post_spec, + state, + spec.SLOTS_PER_EPOCH * 2, + fill_cur_epoch=True, + fill_prev_epoch=True, + ) + blocks.extend([pre_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 + 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) + ]) + + 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) + ]) + assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index 29f9038a8..ff744c4ed 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -1,5 +1,9 @@ from random import Random +from eth2spec.test.helpers.state import ( + next_epoch_via_block, +) + def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Random(4242)): state.inactivity_scores = [rng.randint(minimum, maximum) for _ in range(len(state.validators))] @@ -7,3 +11,22 @@ def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Rando def zero_inactivity_scores(spec, state, rng=None): state.inactivity_scores = [0] * len(state.validators) + + +def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040), fraction=0.25): + """ + ``run_inactivity_scores_test`` runs at the next epoch from `state`. + # We retrieve the proposer of this future state to avoid + # accidentally slashing that validator + """ + future_state = state.copy() + next_epoch_via_block(spec, future_state) + proposer_index = spec.get_beacon_proposer_index(future_state) + selected_count = int(len(state.validators) * fraction) + selected_indices = rng.sample(range(len(state.validators)), selected_count) + if proposer_index in selected_indices: + selected_indices.remove(proposer_index) + for validator_index in selected_indices: + spec.slash_validator(state, validator_index) + + return selected_indices diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 8448b2424..e8b233e1e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,6 +7,7 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): + eligible_indices = queuing_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited for index in range(num_validators): @@ -18,6 +19,10 @@ def set_some_new_deposits(spec, state, rng): # Set ~half of selected to eligible for activation if rng.choice([True, False]): state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) + eligible_indices.append(index) + else: + queuing_indices.append(index) + return eligible_indices, queuing_indices def exit_random_validators(spec, state, rng, fraction=None): From 95f940cc74fed73f24e042f5854e21ad15fc3d8b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 12 Oct 2021 23:33:48 +0800 Subject: [PATCH 05/22] ensure that some of the current sync committee members are the slashed --- .../test/altair/transition/test_transition.py | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index 9996a1278..b56197ab5 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -131,6 +131,11 @@ def _set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(404040 return selected_indices +def _transition_until_fork(spec, state, fork_epoch): + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -494,20 +499,18 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( 1/4 validators are slashed but still active at the fork transition. """ # slash 1/4 validators - selected_indices = slash_some_validators_for_inactivity_scores_test( + slashed_indices = slash_some_validators_for_inactivity_scores_test( spec, state, rng=random.Random(5566), fraction=0.25) - assert len(selected_indices) > 0 + assert len(slashed_indices) > 0 # check if some validators are slashed but still active - for validator_index in selected_indices: + 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) - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert spec.get_current_epoch(state) < fork_epoch @@ -518,12 +521,16 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( 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 the slashed + slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] + assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + # 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 blocks.extend([ post_tag(block) for block in - _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, selected_indices) + _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) ]) # check post state @@ -543,9 +550,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( """ exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) # check pre state assert len(exited_indices) > 0 @@ -594,9 +599,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( """ exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) # check pre state assert len(exited_indices) > 0 @@ -643,9 +646,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). The leaking starts before the fork transition in this case. """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert spec.is_in_inactivity_leak(state) assert spec.get_current_epoch(state) < fork_epoch @@ -677,9 +678,7 @@ def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). The leaking starts at the fork transition in this case. """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert not spec.is_in_inactivity_leak(state) assert spec.get_current_epoch(state) < fork_epoch @@ -711,9 +710,7 @@ def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, p Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). The leaking starts after the fork transition in this case. """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert not spec.is_in_inactivity_leak(state) assert spec.get_current_epoch(state) < fork_epoch @@ -747,9 +744,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos """ Create some deposits before the transition """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) @@ -781,9 +776,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre """ Create a deposit at the transition """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) yield "pre", state From f0980a4ab988bf8c59f24399a40af2a42a07a595 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 13 Oct 2021 00:26:23 +0800 Subject: [PATCH 06/22] Refactoring. Sort tests to specific files. --- .../test/altair/transition/test_activation.py | 210 ++++++++ .../test/altair/transition/test_leaking.py | 106 ++++ .../test/altair/transition/test_slashing.py | 61 +++ .../test/altair/transition/test_transition.py | 502 +----------------- .../eth2spec/test/helpers/fork_transition.py | 130 +++++ tests/generators/transition/main.py | 14 +- 6 files changed, 540 insertions(+), 483 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py create mode 100644 tests/core/pyspec/eth2spec/test/helpers/fork_transition.py diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py new file mode 100644 index 000000000..83933d751 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py @@ -0,0 +1,210 @@ +import random +from eth2spec.test.context import fork_transition_test +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.attestations import next_slots_with_attestations +from eth2spec.test.helpers.deposits import prepare_state_and_deposit +from eth2spec.test.helpers.fork_transition import ( + do_altair_fork, + set_validators_exit_epoch, + state_transition_across_slots, + transition_until_fork, +) +from eth2spec.test.helpers.random import set_some_new_deposits + + +# +# Exit +# + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_one_fourth_exiting_validators_exit_post_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 exiting but still active validators at the fork transition. + """ + exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) + + 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))) + + # 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) + ]) + + # 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 exiting but still active validators at the fork transition. + """ + exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) + + 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 + 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) + ]) + + 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) + + _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + + assert spec.get_current_epoch(state) < fork_epoch + assert len(queuing_indices) > 0 + for validator_index in queuing_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 + 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) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a deposit at the transition + """ + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + # create a new deposit + validator_index = len(state.validators) + amount = post_spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) + blocks = [] + blocks.append(post_tag(block)) + + assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + # 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) + ]) + + # finalize activation_eligibility_epoch + _, blocks_in_epoch, state = next_slots_with_attestations( + post_spec, + state, + spec.SLOTS_PER_EPOCH * 2, + fill_cur_epoch=True, + fill_prev_epoch=True, + ) + blocks.extend([pre_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 + 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) + ]) + + 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) + ]) + assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py new file mode 100644 index 000000000..37c0f80d1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -0,0 +1,106 @@ +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, + state_transition_across_slots, + transition_until_fork, +) + + +@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 + 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) + ]) + + 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 + 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) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) +def test_transition_with_leaking_post_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 after 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 not spec.is_in_inactivity_leak(state) + + # 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) + ]) + + # check state again + assert spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py new file mode 100644 index 000000000..d3b9c5b2f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -0,0 +1,61 @@ +import random +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, + state_transition_across_slots_with_ignoring_proposers, + transition_until_fork, +) +from eth2spec.test.helpers.inactivity_scores import ( + slash_some_validators_for_inactivity_scores_test, +) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) +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_some_validators_for_inactivity_scores_test( + 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: + 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 the slashed + slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] + assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + + # 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 + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, 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 diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index b56197ab5..c6dec8d3e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -3,139 +3,17 @@ from eth2spec.test.context import fork_transition_test from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.state import ( next_epoch_via_signed_block, - next_slot, - state_transition_and_sign_block, - transition_to, -) -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.attestations import next_slots_with_attestations -from eth2spec.test.helpers.random import set_some_new_deposits -from eth2spec.test.helpers.inactivity_scores import ( - slash_some_validators_for_inactivity_scores_test, +from eth2spec.test.helpers.fork_transition import ( + do_altair_fork, + no_blocks, + only_at, + skip_slots, + state_transition_across_slots, ) -def _state_transition_and_sign_block_at_slot(spec, state, deposits=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. - """ - block = build_empty_block(spec, state) - # FIXME: not just passing `deposits` - if deposits is not None: - block.body.deposits = deposits - - 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): - """ - 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: - 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, deposits=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, deposits=deposits) - else: - return state, None - - -def _set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): - """ - Set some valdiators' exit_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): - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) - - @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -151,18 +29,18 @@ 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) + state_transition_across_slots(post_spec, state, to_slot) ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 @@ -192,17 +70,17 @@ 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) + state_transition_across_slots(post_spec, state, to_slot) ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 @@ -234,18 +112,18 @@ 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) + state_transition_across_slots(post_spec, state, to_slot) ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 @@ -277,18 +155,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 @@ -348,7 +226,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 @@ -461,11 +339,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 @@ -490,341 +368,3 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe yield "blocks", blocks yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) -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_some_validators_for_inactivity_scores_test( - 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: - 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 the slashed - slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] - assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) - - # 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 - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, 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 - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_one_fourth_exiting_validators_exit_post_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - 1/4 exiting but still active validators at the fork transition. - """ - exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) - - _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))) - - # 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) - ]) - - # 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 exiting but still active validators at the fork transition. - """ - exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) - - _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 - 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) - ]) - - yield "blocks", blocks - yield "post", state - - -@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 - 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) - ]) - - 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 - 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) - ]) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) -def test_transition_with_leaking_post_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 after 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 not spec.is_in_inactivity_leak(state) - - # 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) - ]) - - # check state again - assert spec.is_in_inactivity_leak(state) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) -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) - - _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) - - assert spec.get_current_epoch(state) < fork_epoch - assert len(queuing_indices) > 0 - for validator_index in queuing_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 - 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) - ]) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) -def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create a deposit at the transition - """ - _transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # create a new deposit - validator_index = len(state.validators) - amount = post_spec.MAX_EFFECTIVE_BALANCE - deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) - - # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) - blocks = [] - blocks.append(post_tag(block)) - - assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - # 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) - ]) - - # finalize activation_eligibility_epoch - _, blocks_in_epoch, state = next_slots_with_attestations( - post_spec, - state, - spec.SLOTS_PER_EPOCH * 2, - fill_cur_epoch=True, - fill_prev_epoch=True, - ) - blocks.extend([pre_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 - 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) - ]) - - 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) - ]) - assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - yield "blocks", blocks - yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py new file mode 100644 index 000000000..3a9977326 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -0,0 +1,130 @@ +import random + +from eth2spec.test.helpers.state import ( + next_slot, + state_transition_and_sign_block, + transition_to, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + build_empty_block, + sign_block, +) + + +def _state_transition_and_sign_block_at_slot(spec, state, deposits=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. + """ + block = build_empty_block(spec, state) + # FIXME: not just passing `deposits` + if deposits is not None: + block.body.deposits = deposits + + 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): + """ + 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: + 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, deposits=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, deposits=deposits) + else: + return state, None + + +def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): + """ + Set some valdiators' exit_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): + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index 2ded56a13..0a9080db3 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -1,7 +1,12 @@ 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_activation as test_altair_activation, + test_leaking as test_altair_leaking, + test_slashing as test_altair_slashing, +) 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 +30,12 @@ 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_activation), + (PHASE0, ALTAIR, test_altair_leaking), + (PHASE0, ALTAIR, test_altair_slashing), +) if __name__ == "__main__": From 53d4fa51871e007b1362637824566d72d2fa3d0f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 13 Oct 2021 02:01:13 +0800 Subject: [PATCH 07/22] Make operation (attester_slashing, proposer_slashing, voluntary_exit) at the fork block --- .../test/altair/transition/test_activation.py | 41 +++++++++- .../test/altair/transition/test_slashing.py | 82 ++++++++++++++++++- .../eth2spec/test/helpers/fork_transition.py | 16 ++-- 3 files changed, 130 insertions(+), 9 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py index 83933d751..91888b0c3 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py @@ -10,6 +10,10 @@ from eth2spec.test.helpers.fork_transition import ( transition_until_fork, ) from eth2spec.test.helpers.random import set_some_new_deposits +from eth2spec.test.helpers.state import ( + transition_to, +) +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits # @@ -114,6 +118,39 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( yield "post", state +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) +def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing at the transition + """ + transition_to(spec, state, spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH) + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + validator_index = 0 + signed_exits = prepare_signed_exits(spec, state, [validator_index]) + operation_dict = {'voluntary_exits': signed_exits} + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) + blocks = [] + blocks.append(post_tag(block)) + + validator = state.validators[validator_index] + assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH + + # 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) + ]) + + yield "blocks", blocks + yield "post", state + + # # Activation # @@ -164,9 +201,9 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre validator_index = len(state.validators) amount = post_spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) - + operation_dict = {'deposits': [deposit]} # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) blocks = [] blocks.append(post_tag(block)) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index d3b9c5b2f..018ce6781 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -1,8 +1,18 @@ import random -from eth2spec.test.context import fork_transition_test +from eth2spec.test.context import ( + always_bls, + fork_transition_test, +) from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.attester_slashings import ( + get_valid_attester_slashing, +) +from eth2spec.test.helpers.proposer_slashings import ( + get_valid_proposer_slashing, +) from eth2spec.test.helpers.fork_transition import ( do_altair_fork, + state_transition_across_slots, state_transition_across_slots_with_ignoring_proposers, transition_until_fork, ) @@ -59,3 +69,73 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( yield "blocks", blocks yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing at the transition + """ + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + # NOTE: it can only be created with pre spec + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'attester_slashings': [attester_slashing]} + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) + blocks = [] + blocks.append(post_tag(block)) + + indices = set(attester_slashing.attestation_1.attesting_indices).intersection( + attester_slashing.attestation_2.attesting_indices + ) + assert len(indices) > 0 + for validator_index in indices: + assert state.validators[validator_index].slashed + + # 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) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing at the transition + """ + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + # NOTE: it can only be created with pre spec + proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'proposer_slashings': [proposer_slashing]} + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) + blocks = [] + blocks.append(post_tag(block)) + + slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] + assert slashed_proposer.slashed + + # 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) + ]) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 3a9977326..e0e7d028b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -12,7 +12,10 @@ from eth2spec.test.helpers.block import ( ) -def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): +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 @@ -21,9 +24,10 @@ def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): Used to produce a block during an irregular state transition. """ block = build_empty_block(spec, state) - # FIXME: not just passing `deposits` - if deposits is not None: - block.body.deposits = deposits + # we can't just pass `body` because randao_reveal and eth1_data was set in `build_empty_block` + if operation_dict is not None: + for key, value in operation_dict.items(): + setattr(block.body, key, value) assert state.latest_block_header.slot < block.slot assert state.slot == block.slot @@ -93,7 +97,7 @@ def state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, next_slot(spec, state) -def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, deposits=None): +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 @@ -106,7 +110,7 @@ def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, deposits 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, deposits=deposits) + return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict) else: return state, None From 67da1ba2bf24f0cec1a233a2bad50b189b65cec5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 13 Oct 2021 02:35:47 +0800 Subject: [PATCH 08/22] Minor refactoring - sanity check: deposit operation is independent of spec fork versions - refactoring - add comments --- .../test/altair/transition/test_activation.py | 43 ++++++------------- .../test/altair/transition/test_leaking.py | 20 ++------- .../test/altair/transition/test_slashing.py | 14 ++---- .../test/altair/transition/test_transition.py | 19 ++------ .../eth2spec/test/helpers/fork_transition.py | 14 ++++-- 5 files changed, 34 insertions(+), 76 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py index 91888b0c3..df8ac6934 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py @@ -8,6 +8,7 @@ from eth2spec.test.helpers.fork_transition import ( set_validators_exit_epoch, state_transition_across_slots, transition_until_fork, + transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.random import set_some_new_deposits from eth2spec.test.helpers.state import ( @@ -52,11 +53,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) # 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) # check state for index in exited_indices: @@ -108,11 +105,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) # 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) yield "blocks", blocks yield "post", state @@ -121,7 +114,8 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ - Create an attester slashing at the transition + Create an attester slashing at the transition. + fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. """ transition_to(spec, state, spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH) transition_until_fork(spec, state, fork_epoch) @@ -141,11 +135,7 @@ def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_sp assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH # 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) yield "blocks", blocks yield "post", state @@ -178,11 +168,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos 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) yield "blocks", blocks yield "post", state @@ -201,6 +187,9 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre validator_index = len(state.validators) amount = post_spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) + deposit_old = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + # sanity check: deposit operation is independent of spec fork versions + assert deposit_old == deposit operation_dict = {'deposits': [deposit]} # irregular state transition to handle fork: state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) @@ -210,11 +199,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) # 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) # finalize activation_eligibility_epoch _, blocks_in_epoch, state = next_slots_with_attestations( @@ -228,11 +213,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch # 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.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py index 37c0f80d1..086e43a93 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -2,8 +2,8 @@ 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, - state_transition_across_slots, transition_until_fork, + transition_to_next_epoch_and_append_blocks, ) @@ -29,11 +29,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr assert spec.is_in_inactivity_leak(state) # 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) yield "blocks", blocks yield "post", state @@ -61,11 +57,7 @@ def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre assert spec.is_in_inactivity_leak(state) # 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) yield "blocks", blocks yield "post", state @@ -93,11 +85,7 @@ def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, p assert not spec.is_in_inactivity_leak(state) # 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) # check state again assert spec.is_in_inactivity_leak(state) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 018ce6781..cc22f5c22 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -12,9 +12,9 @@ from eth2spec.test.helpers.proposer_slashings import ( ) from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - state_transition_across_slots, state_transition_across_slots_with_ignoring_proposers, transition_until_fork, + transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.inactivity_scores import ( slash_some_validators_for_inactivity_scores_test, @@ -98,11 +98,7 @@ def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post assert state.validators[validator_index].slashed # 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) yield "blocks", blocks yield "post", state @@ -131,11 +127,7 @@ def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post assert slashed_proposer.slashed # 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) yield "blocks", blocks yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index c6dec8d3e..fe6248c5f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -11,6 +11,7 @@ from eth2spec.test.helpers.fork_transition import ( only_at, skip_slots, state_transition_across_slots, + transition_to_next_epoch_and_append_blocks, ) @@ -37,11 +38,7 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag 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 @@ -77,11 +74,7 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, 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 @@ -120,11 +113,7 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp 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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index e0e7d028b..ea5419d0d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -14,7 +14,6 @@ from eth2spec.test.helpers.block import ( def _state_transition_and_sign_block_at_slot(spec, state, - *, operation_dict=None): """ Cribbed from ``transition_unsigned_block`` helper @@ -24,7 +23,8 @@ def _state_transition_and_sign_block_at_slot(spec, Used to produce a block during an irregular state transition. """ block = build_empty_block(spec, state) - # we can't just pass `body` because randao_reveal and eth1_data was set in `build_empty_block` + # 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. if operation_dict is not None: for key, value in operation_dict.items(): setattr(block.body, key, value) @@ -117,7 +117,7 @@ def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operatio def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): """ - Set some valdiators' exit_epoch. + 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) @@ -132,3 +132,11 @@ def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(4040404 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_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks): + to_slot = spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(spec, state, to_slot) + ]) From 9879e0475dae9d06acb5b6c93e79e5e3c8a9d303 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 00:27:08 +0800 Subject: [PATCH 09/22] Add random test format doc --- tests/formats/random/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/formats/random/README.md diff --git a/tests/formats/random/README.md b/tests/formats/random/README.md new file mode 100644 index 000000000..54b2c1a23 --- /dev/null +++ b/tests/formats/random/README.md @@ -0,0 +1,7 @@ +# Random tests + +The random tests are generated with various randomized states and blocks. + +## Test case format + +- `random` handler: same as the [`blocks`](../sanity/blocks.md) handler test case format from sanity tests. From cbba5426fea2fe3b0532e71339044981d2cfe6ab Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 20:07:07 +0800 Subject: [PATCH 10/22] PR feedback --- ...ation.py => test_activations_and_exits.py} | 25 ++++++++++++------- .../test/altair/transition/test_slashing.py | 9 +++++-- .../test/helpers/inactivity_scores.py | 4 +-- .../pyspec/eth2spec/test/helpers/random.py | 3 ++- 4 files changed, 27 insertions(+), 14 deletions(-) rename tests/core/pyspec/eth2spec/test/altair/transition/{test_activation.py => test_activations_and_exits.py} (88%) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py similarity index 88% rename from tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py rename to tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index df8ac6934..50e97d24e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -11,9 +11,6 @@ from eth2spec.test.helpers.fork_transition import ( transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.random import set_some_new_deposits -from eth2spec.test.helpers.state import ( - transition_to, -) from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits @@ -22,8 +19,12 @@ from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits # @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_one_fourth_exiting_validators_exit_post_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): +def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag): """ 1/4 exiting but still active validators at the fork transition. """ @@ -51,6 +52,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( # 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) @@ -67,8 +69,12 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( @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): +def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag): """ 1/4 exiting but still active validators at the fork transition. """ @@ -117,7 +123,8 @@ def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_sp Create an attester slashing at the transition. fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. """ - transition_to(spec, state, spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH) + # Fast forward to the future epoch so that validator can do voluntary exit + state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH transition_until_fork(spec, state, fork_epoch) yield "pre", state @@ -209,7 +216,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre fill_cur_epoch=True, fill_prev_epoch=True, ) - blocks.extend([pre_tag(block) for block in blocks_in_epoch]) + 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 diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index cc22f5c22..86b78bd3e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -22,8 +22,12 @@ from eth2spec.test.helpers.inactivity_scores import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) -def test_transition_with_one_fourth_slashed_active_validators_pre_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): +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. """ @@ -53,6 +57,7 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( # ensure that some of the current sync committee members are the 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 to_slot = post_spec.SLOTS_PER_EPOCH + state.slot diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index ff744c4ed..02aec7105 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -16,8 +16,8 @@ def zero_inactivity_scores(spec, state, rng=None): def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040), fraction=0.25): """ ``run_inactivity_scores_test`` runs at the next epoch from `state`. - # We retrieve the proposer of this future state to avoid - # accidentally slashing that validator + We retrieve the proposer of this future state to avoid + accidentally slashing that validator """ future_state = state.copy() next_epoch_via_block(spec, future_state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index e8b233e1e..cd662d09d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,7 +7,8 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): - eligible_indices = queuing_indices = [] + eligible_indices = [] + queuing_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited for index in range(num_validators): From 49bf78d43148241bcbec247a73f75d575b522134 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 21:45:33 +0800 Subject: [PATCH 11/22] PR feedback --- .../test_process_inactivity_updates.py | 15 ++++++- .../transition/test_activations_and_exits.py | 30 ++++++++------ .../test/altair/transition/test_leaking.py | 35 +--------------- .../test/altair/transition/test_slashing.py | 18 ++++---- .../eth2spec/test/helpers/fork_transition.py | 20 ++++++--- .../test/helpers/inactivity_scores.py | 23 ----------- .../pyspec/eth2spec/test/helpers/random.py | 41 ++++++++++++++----- tests/generators/transition/main.py | 4 +- 8 files changed, 90 insertions(+), 96 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index f262bcdd2..1ac622dd9 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -4,7 +4,6 @@ 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, - slash_some_validators_for_inactivity_scores_test, ) from eth2spec.test.helpers.state import ( next_epoch, @@ -205,6 +204,20 @@ def test_random_inactivity_scores_full_participation_leaking(spec, state): assert spec.is_in_inactivity_leak(state) +def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040)): + # ``run_inactivity_scores_test`` runs at the next epoch from `state`. + # We retrieve the proposer of this future state to avoid + # accidentally slashing that validator + future_state = state.copy() + next_epoch_via_block(spec, future_state) + + proposer_index = spec.get_beacon_proposer_index(future_state) + # Slash ~1/4 of validaors + for validator_index in range(len(state.validators)): + if rng.choice(range(4)) == 0 and validator_index != proposer_index: + spec.slash_validator(state, validator_index) + + @with_altair_and_later @spec_state_test def test_some_slashed_zero_scores_full_participation(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 50e97d24e..573f7fba1 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -5,12 +5,14 @@ from eth2spec.test.helpers.attestations import next_slots_with_attestations from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - set_validators_exit_epoch, state_transition_across_slots, transition_until_fork, transition_to_next_epoch_and_append_blocks, ) -from eth2spec.test.helpers.random import set_some_new_deposits +from eth2spec.test.helpers.random import ( + exit_random_validators, + set_some_new_deposits, +) from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits @@ -26,9 +28,11 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, pre_tag, post_tag): """ - 1/4 exiting but still active validators at the fork transition. + 1/4 validators initiated voluntary exit before the fork, + and are exiting but still active *after* the fork transition. """ - exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) + exited_indices = exit_random_validators( + spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=10, forward=False) transition_until_fork(spec, state, fork_epoch) @@ -55,7 +59,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, 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) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) # check state for index in exited_indices: @@ -76,9 +80,11 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, pre_tag, post_tag): """ - 1/4 exiting but still active validators at the fork transition. + 1/4 validators initiated voluntary exit before the fork, + and being exited and inactive *right after* the fork transition. """ - exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) + exited_indices = exit_random_validators( + spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=fork_epoch, forward=False) transition_until_fork(spec, state, fork_epoch) @@ -111,7 +117,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, 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) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -120,7 +126,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, @fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ - Create an attester slashing at the transition. + Create a voluntary exit at 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 @@ -142,7 +148,7 @@ def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_sp assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -175,7 +181,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos 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) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -220,7 +226,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre 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) + 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 diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py index 086e43a93..6cdac1661 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -29,7 +29,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr 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) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -57,38 +57,7 @@ def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre 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) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) -def test_transition_with_leaking_post_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 after 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 not 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) - - # check state again - assert spec.is_in_inactivity_leak(state) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 86b78bd3e..6c9a8371b 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -16,8 +16,8 @@ from eth2spec.test.helpers.fork_transition import ( transition_until_fork, transition_to_next_epoch_and_append_blocks, ) -from eth2spec.test.helpers.inactivity_scores import ( - slash_some_validators_for_inactivity_scores_test, +from eth2spec.test.helpers.random import ( + slash_random_validators, ) @@ -32,8 +32,7 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, 1/4 validators are slashed but still active at the fork transition. """ # slash 1/4 validators - slashed_indices = slash_some_validators_for_inactivity_scores_test( - spec, state, rng=random.Random(5566), fraction=0.25) + 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 @@ -50,11 +49,9 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(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)) + state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) - # ensure that some of the current sync committee members are the slashed + # 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))) @@ -62,6 +59,7 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, # 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 + blocks = [] blocks.extend([ post_tag(block) for block in state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) @@ -103,7 +101,7 @@ def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post assert state.validators[validator_index].slashed # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -132,7 +130,7 @@ def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post assert slashed_proposer.slashed # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index ea5419d0d..277428bcf 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -21,11 +21,15 @@ def _state_transition_and_sign_block_at_slot(spec, been applied to ``state``. Used to produce a block during an irregular state transition. + + The optional `operation_dict` is a dict of {'': }. + 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) - # 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. - if operation_dict is not None: + + if operation_dict: for key, value in operation_dict.items(): setattr(block.body, key, value) @@ -134,9 +138,15 @@ def transition_until_fork(spec, state, fork_epoch): transition_to(spec, state, to_slot) -def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks): +def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, only_last_block=False): to_slot = spec.SLOTS_PER_EPOCH + state.slot + + if only_last_block: + block_filter = only_at(to_slot) + else: + block_filter = _all_blocks + blocks.extend([ post_tag(block) for block in - state_transition_across_slots(spec, state, to_slot) + state_transition_across_slots(spec, state, to_slot, block_filter=block_filter) ]) diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index 02aec7105..29f9038a8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -1,9 +1,5 @@ from random import Random -from eth2spec.test.helpers.state import ( - next_epoch_via_block, -) - def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Random(4242)): state.inactivity_scores = [rng.randint(minimum, maximum) for _ in range(len(state.validators))] @@ -11,22 +7,3 @@ def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Rando def zero_inactivity_scores(spec, state, rng=None): state.inactivity_scores = [0] * len(state.validators) - - -def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040), fraction=0.25): - """ - ``run_inactivity_scores_test`` runs at the next epoch from `state`. - We retrieve the proposer of this future state to avoid - accidentally slashing that validator - """ - future_state = state.copy() - next_epoch_via_block(spec, future_state) - proposer_index = spec.get_beacon_proposer_index(future_state) - selected_count = int(len(state.validators) * fraction) - selected_indices = rng.sample(range(len(state.validators)), selected_count) - if proposer_index in selected_indices: - selected_indices.remove(proposer_index) - for validator_index in selected_indices: - spec.slash_validator(state, validator_index) - - return selected_indices diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index cd662d09d..4086431c0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -26,29 +26,47 @@ def set_some_new_deposits(spec, state, rng): return eligible_indices, queuing_indices -def exit_random_validators(spec, state, rng, fraction=None): +def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, withdrawable_epoch=None, forward=True): + """ + 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 fraction is None: # Exit ~1/2 fraction = 0.5 - 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 forward: + 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) 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): @@ -56,11 +74,14 @@ def slash_random_validators(spec, state, rng, fraction=None): # Slash ~1/2 of validators 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): diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index 0a9080db3..efe00995e 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -3,7 +3,7 @@ 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, - test_activation as test_altair_activation, + test_activations_and_exits as test_altair_activations_and_exits, test_leaking as test_altair_leaking, test_slashing as test_altair_slashing, ) @@ -32,7 +32,7 @@ def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_n TRANSITION_TESTS = ( (PHASE0, ALTAIR, test_altair_transition), - (PHASE0, ALTAIR, test_altair_activation), + (PHASE0, ALTAIR, test_altair_activations_and_exits), (PHASE0, ALTAIR, test_altair_leaking), (PHASE0, ALTAIR, test_altair_slashing), ) From 3a242a1e0b7534bad0952d786c3235de95934b35 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 23:29:20 +0800 Subject: [PATCH 12/22] Refactor and add test cases of having operation right before the fork --- .../transition/test_activations_and_exits.py | 89 --------- .../test/altair/transition/test_operations.py | 176 ++++++++++++++++++ .../test/altair/transition/test_slashing.py | 70 ------- .../eth2spec/test/helpers/fork_transition.py | 159 +++++++++++++++- tests/generators/transition/main.py | 2 + 5 files changed, 331 insertions(+), 165 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 573f7fba1..8f4cad2c5 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -1,11 +1,8 @@ import random from eth2spec.test.context import fork_transition_test from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.test.helpers.attestations import next_slots_with_attestations -from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - state_transition_across_slots, transition_until_fork, transition_to_next_epoch_and_append_blocks, ) @@ -13,7 +10,6 @@ from eth2spec.test.helpers.random import ( exit_random_validators, set_some_new_deposits, ) -from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits # @@ -123,37 +119,6 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) -def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create a voluntary exit at 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 - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - validator_index = 0 - signed_exits = prepare_signed_exits(spec, state, [validator_index]) - operation_dict = {'voluntary_exits': signed_exits} - - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - validator = state.validators[validator_index] - assert validator.exit_epoch < post_spec.FAR_FUTURE_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) - - yield "blocks", blocks - yield "post", state - - # # Activation # @@ -185,57 +150,3 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos yield "blocks", blocks yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create a deposit at the transition - """ - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # create a new deposit - validator_index = len(state.validators) - amount = post_spec.MAX_EFFECTIVE_BALANCE - deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) - deposit_old = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) - # sanity check: deposit operation is independent of spec fork versions - assert deposit_old == deposit - operation_dict = {'deposits': [deposit]} - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - # 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, - 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) - ]) - assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - yield "blocks", blocks - yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py new file mode 100644 index 000000000..09d3c25c5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py @@ -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 ( + OperetionType, + 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=OperetionType.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=OperetionType.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=OperetionType.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=OperetionType.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=OperetionType.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=OperetionType.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=OperetionType.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=OperetionType.VOLUNTARY_EXIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 6c9a8371b..0a5c7abeb 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -1,20 +1,12 @@ import random from eth2spec.test.context import ( - always_bls, fork_transition_test, ) from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.test.helpers.attester_slashings import ( - get_valid_attester_slashing, -) -from eth2spec.test.helpers.proposer_slashings import ( - get_valid_proposer_slashing, -) from eth2spec.test.helpers.fork_transition import ( do_altair_fork, state_transition_across_slots_with_ignoring_proposers, transition_until_fork, - transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.random import ( slash_random_validators, @@ -72,65 +64,3 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, yield "blocks", blocks yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -@always_bls -def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create an attester slashing at the transition - """ - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # NOTE: it can only be created with pre spec - attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - operation_dict = {'attester_slashings': [attester_slashing]} - - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - indices = set(attester_slashing.attestation_1.attesting_indices).intersection( - attester_slashing.attestation_2.attesting_indices - ) - assert len(indices) > 0 - for validator_index in indices: - assert state.validators[validator_index].slashed - - # 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) -@always_bls -def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create an attester slashing at the transition - """ - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # NOTE: it can only be created with pre spec - proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) - operation_dict = {'proposer_slashings': [proposer_slashing]} - - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] - assert slashed_proposer.slashed - - # 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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 277428bcf..7f2b913e6 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -1,15 +1,41 @@ +from enum import Enum, auto import random -from eth2spec.test.helpers.state import ( - next_slot, - state_transition_and_sign_block, - transition_to, +from eth2spec.test.helpers.attester_slashings import ( + get_valid_attester_slashing, ) +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 OperetionType(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, @@ -30,8 +56,7 @@ def _state_transition_and_sign_block_at_slot(spec, block = build_empty_block(spec, state) if operation_dict: - for key, value in operation_dict.items(): - setattr(block.body, key, value) + _set_operations_by_dict(block, operation_dict) assert state.latest_block_header.slot < block.slot assert state.slot == block.slot @@ -138,6 +163,11 @@ def transition_until_fork(spec, state, fork_epoch): 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): to_slot = spec.SLOTS_PER_EPOCH + state.slot @@ -150,3 +180,120 @@ def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, on post_tag(block) for block in state_transition_across_slots(spec, state, to_slot, block_filter=block_filter) ]) + + +def run_transition_with_operation(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type, + 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 + + blocks = [] + + if is_at_fork: + transition_until_fork(spec, state, fork_epoch) + + if is_right_before_fork: + _transition_until_fork_minus_one(spec, state, fork_epoch) + + # prepare operation + selected_validator_index = None + if operation_type == OperetionType.PROPOSER_SLASHING: + proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'proposer_slashings': [proposer_slashing]} + elif operation_type == OperetionType.ATTESTER_SLASHING: + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'attester_slashings': [attester_slashing]} + elif operation_type == OperetionType.DEPOSIT: + # create a new deposit + selected_validator_index = len(state.validators) + amount = post_spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(post_spec, state, selected_validator_index, amount, signed=True) + operation_dict = {'deposits': [deposit]} + elif operation_type == OperetionType.VOLUNTARY_EXIT: + selected_validator_index = 0 + signed_exits = prepare_signed_exits(spec, state, [selected_validator_index]) + operation_dict = {'voluntary_exits': signed_exits} + + 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 == OperetionType.PROPOSER_SLASHING: + slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] + assert slashed_proposer.slashed + elif operation_type == OperetionType.ATTESTER_SLASHING: + indices = set(attester_slashing.attestation_1.attesting_indices).intersection( + attester_slashing.attestation_2.attesting_indices + ) + assert len(indices) > 0 + for validator_index in indices: + assert state.validators[validator_index].slashed + elif operation_type == OperetionType.DEPOSIT: + assert not post_spec.is_active_validator( + state.validators[selected_validator_index], + post_spec.get_current_epoch(state) + ) + elif operation_type == OperetionType.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 == OperetionType.DEPOSIT: + _transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index) + else: + # 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 + + +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)) diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index efe00995e..a850a7f45 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -6,6 +6,7 @@ from eth2spec.test.altair.transition import ( 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 @@ -35,6 +36,7 @@ TRANSITION_TESTS = ( (PHASE0, ALTAIR, test_altair_activations_and_exits), (PHASE0, ALTAIR, test_altair_leaking), (PHASE0, ALTAIR, test_altair_slashing), + (PHASE0, ALTAIR, test_altair_operations), ) From f62167c4abe6fec6784bc937b841aa77a1e5e9b0 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 00:30:24 +0800 Subject: [PATCH 13/22] pr feedback --- .../test/altair/transition/test_activations_and_exits.py | 2 +- tests/core/pyspec/eth2spec/test/helpers/random.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 8f4cad2c5..86742042f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -131,7 +131,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos """ transition_until_fork(spec, state, fork_epoch) - _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) assert spec.get_current_epoch(state) < fork_epoch assert len(queuing_indices) > 0 diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 4086431c0..6d51d812e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,7 +7,6 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): - eligible_indices = [] queuing_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited @@ -20,10 +19,9 @@ def set_some_new_deposits(spec, state, rng): # Set ~half of selected to eligible for activation if rng.choice([True, False]): state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) - eligible_indices.append(index) else: queuing_indices.append(index) - return eligible_indices, queuing_indices + return queuing_indices def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, withdrawable_epoch=None, forward=True): From 52235a9e40eab6be47cdf3b5da13c80fc267a6d6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 00:42:14 +0800 Subject: [PATCH 14/22] minor: Use pre spec for running `prepare_state_and_deposit` --- .../core/pyspec/eth2spec/test/helpers/fork_transition.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 7f2b913e6..47e0c803a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -190,6 +190,10 @@ def run_transition_with_operation(state, 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 @@ -213,8 +217,8 @@ def run_transition_with_operation(state, elif operation_type == OperetionType.DEPOSIT: # create a new deposit selected_validator_index = len(state.validators) - amount = post_spec.MAX_EFFECTIVE_BALANCE - deposit = prepare_state_and_deposit(post_spec, state, selected_validator_index, amount, signed=True) + 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 == OperetionType.VOLUNTARY_EXIT: selected_validator_index = 0 From a4e5d50660f5af57e9c8b75987f7478f04e76098 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 11:57:10 +0800 Subject: [PATCH 15/22] Fix/ignore mainnet preset cases --- .../transition/test_activations_and_exits.py | 7 ++++++- .../test/altair/transition/test_slashing.py | 3 +++ .../eth2spec/test/helpers/fork_transition.py | 15 ++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 86742042f..7ef11f629 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -1,5 +1,9 @@ import random -from eth2spec.test.context import fork_transition_test +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, @@ -17,6 +21,7 @@ from eth2spec.test.helpers.random import ( # @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_presets([MINIMAL], reason="only test with non-full committee") def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, fork_epoch, spec, diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 0a5c7abeb..20ab89c1a 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -1,6 +1,8 @@ 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 ( @@ -14,6 +16,7 @@ from eth2spec.test.helpers.random import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) +@with_presets([MINIMAL], reason="only test with non-full committee") def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, fork_epoch, spec, diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 47e0c803a..63a92fce2 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -198,18 +198,21 @@ def run_transition_with_operation(state, is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1 assert is_at_fork or is_right_before_fork - blocks = [] - if is_at_fork: transition_until_fork(spec, state, fork_epoch) - - if is_right_before_fork: + elif is_right_before_fork: _transition_until_fork_minus_one(spec, state, fork_epoch) # prepare operation selected_validator_index = None if operation_type == OperetionType.PROPOSER_SLASHING: - proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + # 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) + 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]} elif operation_type == OperetionType.ATTESTER_SLASHING: attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) @@ -225,6 +228,8 @@ def run_transition_with_operation(state, 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) From 162711ea564fb376c848f2b839140160f8f6722e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 21:08:21 +0800 Subject: [PATCH 16/22] PR feedback. Rework `transition_to_next_epoch_and_append_blocks` a bit --- .../transition/test_activations_and_exits.py | 25 +++-- .../test/altair/transition/test_operations.py | 18 ++-- .../test/altair/transition/test_slashing.py | 18 ++-- .../eth2spec/test/helpers/fork_transition.py | 97 ++++++++++++------- .../pyspec/eth2spec/test/helpers/random.py | 21 ++-- 5 files changed, 108 insertions(+), 71 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 7ef11f629..e91493250 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -21,7 +21,8 @@ from eth2spec.test.helpers.random import ( # @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, fork_epoch, 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. """ 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) @@ -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. """ 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) @@ -136,11 +149,11 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos """ 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 len(queuing_indices) > 0 - for validator_index in queuing_indices: + 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 diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py index 09d3c25c5..e19c57fb1 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py @@ -4,7 +4,7 @@ from eth2spec.test.context import ( ) from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( - OperetionType, + OperationType, run_transition_with_operation, ) @@ -26,7 +26,7 @@ def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, s post_spec, pre_tag, post_tag, - operation_type=OperetionType.PROPOSER_SLASHING, + operation_type=OperationType.PROPOSER_SLASHING, 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, pre_tag, post_tag, - operation_type=OperetionType.PROPOSER_SLASHING, + operation_type=OperationType.PROPOSER_SLASHING, 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, pre_tag, post_tag, - operation_type=OperetionType.ATTESTER_SLASHING, + operation_type=OperationType.ATTESTER_SLASHING, 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, pre_tag, post_tag, - operation_type=OperetionType.ATTESTER_SLASHING, + operation_type=OperationType.ATTESTER_SLASHING, 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, pre_tag, post_tag, - operation_type=OperetionType.DEPOSIT, + operation_type=OperationType.DEPOSIT, 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, pre_tag, post_tag, - operation_type=OperetionType.DEPOSIT, + operation_type=OperationType.DEPOSIT, 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, pre_tag, post_tag, - operation_type=OperetionType.VOLUNTARY_EXIT, + operation_type=OperationType.VOLUNTARY_EXIT, 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, pre_tag, post_tag, - operation_type=OperetionType.VOLUNTARY_EXIT, + operation_type=OperationType.VOLUNTARY_EXIT, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, ) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 20ab89c1a..975733eeb 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -7,7 +7,7 @@ from eth2spec.test.context import ( from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - state_transition_across_slots_with_ignoring_proposers, + transition_to_next_epoch_and_append_blocks, transition_until_fork, ) from eth2spec.test.helpers.random import ( @@ -16,7 +16,8 @@ from eth2spec.test.helpers.random import ( @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, fork_epoch, 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))) # 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 blocks = [] - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) - ]) + 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: diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 63a92fce2..947954b80 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -1,8 +1,7 @@ from enum import Enum, auto -import random 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.block import ( @@ -26,7 +25,7 @@ from eth2spec.test.helpers.voluntary_exits import ( ) -class OperetionType(Enum): +class OperationType(Enum): PROPOSER_SLASHING = auto() ATTESTER_SLASHING = auto() DEPOSIT = auto() @@ -104,7 +103,11 @@ def state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks 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` 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 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) @@ -144,20 +151,6 @@ def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operatio 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): to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 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) -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 if only_last_block: @@ -176,9 +174,20 @@ def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, on 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 - 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: _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 operation_type == OperetionType.PROPOSER_SLASHING: + 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) - 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]} - elif operation_type == OperetionType.ATTESTER_SLASHING: - attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - operation_dict = {'attester_slashings': [attester_slashing]} - elif operation_type == OperetionType.DEPOSIT: + 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 == OperetionType.VOLUNTARY_EXIT: + 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} @@ -238,22 +254,23 @@ def run_transition_with_operation(state, blocks.append(pre_tag(signed_block)) 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] 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( 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 == OperetionType.DEPOSIT: + 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 == OperetionType.VOLUNTARY_EXIT: + elif operation_type == OperationType.VOLUNTARY_EXIT: validator = state.validators[selected_validator_index] assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH @@ -271,11 +288,21 @@ def run_transition_with_operation(state, _check_state() # 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) 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) + 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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 6d51d812e..4f6139989 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,7 +7,7 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): - queuing_indices = [] + deposited_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited for index in range(num_validators): @@ -16,24 +16,21 @@ 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: - queuing_indices.append(index) - return queuing_indices + # The validators that just made a deposit + 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. 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 spec.get_current_epoch(state) < 5: # 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 -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 From be6d2017bb93c0d6c1a49b7653bdc56afdd7030a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:05:07 +0800 Subject: [PATCH 17/22] fix typo --- .../test/altair/transition/test_activations_and_exits.py | 2 +- .../pyspec/eth2spec/test/altair/transition/test_slashing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index e91493250..2ccecec1a 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -22,7 +22,7 @@ from eth2spec.test.helpers.random import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) @with_presets([MINIMAL], - reason="only test with enough validators such that at lease one exited index is not in sync committee") + 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, diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 975733eeb..211a4fbfe 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -17,7 +17,7 @@ from eth2spec.test.helpers.random import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) @with_presets([MINIMAL], - reason="only test with enough validators such that at lease one exited index is not in sync committee") + 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, From 40869d6e39bd84cba0b371d23b8011b137bb7369 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:14:30 +0800 Subject: [PATCH 18/22] PR feedback on `exit_random_validators` helper --- .../altair/transition/test_activations_and_exits.py | 4 ++-- tests/core/pyspec/eth2spec/test/helpers/random.py | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 2ccecec1a..25a76a686 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -39,7 +39,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, rng=random.Random(5566), fraction=0.25, exit_epoch=10, - forward=False, + from_epoch=spec.get_current_epoch(state), ) transition_until_fork(spec, state, fork_epoch) @@ -97,7 +97,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, rng=random.Random(5566), fraction=0.25, exit_epoch=fork_epoch, - forward=False, + from_epoch=spec.get_current_epoch(state), ) transition_until_fork(spec, state, fork_epoch) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 4f6139989..1332d1e90 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -25,17 +25,18 @@ def set_some_new_deposits(spec, state, rng): return deposited_indices -def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, forward=True): +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 exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch. """ - if forward: - 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 from_epoch is None: + from_epoch = spec.MAX_SEED_LOOKAHEAD + 1 + epoch_diff = from_epoch - 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 = [] From b0b3733243c15a45e030b9830302b5097e63cbfe Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:18:13 +0800 Subject: [PATCH 19/22] Fix `randomize_state` default params --- tests/core/pyspec/eth2spec/test/helpers/random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 1332d1e90..049bb1a0d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -142,7 +142,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) From 7480fad8cd2cccea758902e6d8b89195fe60a926 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:46:16 +0800 Subject: [PATCH 20/22] Add `test_transition_with_activation_at_fork_epoch` --- .../transition/test_activations_and_exits.py | 36 +++++++++++++++++++ .../pyspec/eth2spec/test/helpers/random.py | 20 +++++++++++ 2 files changed, 56 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 25a76a686..12aa815ad 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -12,6 +12,7 @@ from eth2spec.test.helpers.fork_transition import ( ) from eth2spec.test.helpers.random import ( exit_random_validators, + set_some_activations, set_some_new_deposits, ) @@ -168,3 +169,38 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos 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 diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 049bb1a0d..bf603b2c8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -6,6 +6,26 @@ 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) From e70ef11b4d32473caf989ea3f61d09d596800767 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:49:43 +0800 Subject: [PATCH 21/22] Fix SSZ underflow --- tests/core/pyspec/eth2spec/test/helpers/random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index bf603b2c8..0dc446d19 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -53,7 +53,7 @@ def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, with """ if from_epoch is None: from_epoch = spec.MAX_SEED_LOOKAHEAD + 1 - epoch_diff = from_epoch - spec.get_current_epoch(state) + 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) From a05a78246f7a92ab4a67f04093c1c6c169add8fd Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 15 Oct 2021 09:47:24 -0600 Subject: [PATCH 22/22] bump version.txt to 1.1.3 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index 8428158dc..9c1218c20 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.2 \ No newline at end of file +1.1.3 \ No newline at end of file